From 4f73f98a42f82fabcc964029831d2fcb0fbde473 Mon Sep 17 00:00:00 2001 From: Marko Ristin Date: Fri, 31 Jul 2020 18:18:44 +0200 Subject: [PATCH] Add flag for allowing one-liners (#56) This patch adds a flag so that users can allow one-liner commit messages. Previously, the header/body split was enforced. --- .github/workflows/build-and-test.yml | 8 ++ README.md | 24 +++++- action.yml | 4 + dist/index.js | 69 +++++++++++++---- .../OpinionatedCommitMessage.Tests.ps1 | 54 ++++++++++++- local/powershell/OpinionatedCommitMessage.ps1 | 62 +++++++++++---- .../OpinionatedCommitMessage.ps1.template | 60 +++++++++++---- package.json | 2 +- src/__tests__/inspection.test.ts | 76 ++++++++++++++----- src/__tests__/main.test.ts | 14 ++++ src/input.ts | 12 +++ src/inspection.ts | 48 +++++++++--- src/mainImpl.ts | 20 ++++- 13 files changed, 375 insertions(+), 78 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 914bdcb..688d449 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -26,3 +26,11 @@ jobs: with: additional-verbs: 'chrusimusi, unit-test' path-to-additional-verbs: src/additional-verbs.txt + + test-allow-one-liners: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: ./ + with: + allow-one-liners: 'true' diff --git a/README.md b/README.md index 1d06fca..4b45dfa 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check - uses: mristin/opinionated-commit-message@v2.0.0-pre1 + uses: mristin/opinionated-commit-message@v2 ``` ## Checked Events @@ -99,7 +99,7 @@ string in the workflow file. For example: ```yaml steps: - name: Check - uses: mristin/opinionated-commit-message@v2.0.0-pre1 + uses: mristin/opinionated-commit-message@v2 with: additional-verbs: 'chrusimusi, unit-test' ``` @@ -111,14 +111,30 @@ as input: ```yaml steps: - name: Check - uses: mristin/opinionated-commit-message@v2.0.0-pre1 + uses: mristin/opinionated-commit-message@v2 with: path-to-additional-verbs: 'src/additional-verbs.txt' ``` +## One-liners + +Usually, you need to write elaborate commit messages with a shorter header +and more verbose body for an informative Git history. However, this might be +too rigid for certain projects. + +You can allow one-liner commit messages by setting the flag `allow-one-liners`: + +```yaml + steps: + - name: Check + uses: mristin/opinionated-commit-message@v2 + with: + allow-one-liners: 'true' +``` + ## Local Usage -We translated the opinionanted-commit-message to a powershell script so that +We translated the opinionated-commit-message to a powershell script so that you can include it in your local pre-commit and pre-push checks. The script is available at: [`local/powershell/OpinionatedCommitMessage.ps1`]( diff --git a/action.yml b/action.yml index 1a55e5a..df7d37e 100644 --- a/action.yml +++ b/action.yml @@ -16,3 +16,7 @@ inputs: description: 'Path to the file containing the additional verbs in imperative mood to be accepted in checks' required: false default: '' + allow-one-liners: + description: 'If set to "true", allows one-liner commit messages without body' + required: false + default: '' diff --git a/dist/index.js b/dist/index.js index 0421353..289d1b9 100644 --- a/dist/index.js +++ b/dist/index.js @@ -560,11 +560,16 @@ function splitLines(message) { } function splitSubjectBody(lines) { const result = { errors: [] }; - if (lines.length < 3) { - result.errors.push(`Expected at least three lines (subject, empty, body), ` + + if (lines.length === 0 || lines.length === 1) { + result.errors.push('Expected at least three lines (subject, empty, body), ' + `but got: ${lines.length}`); return result; } + else if (lines.length === 2) { + result.errors.push('Expected at least three lines (subject, empty, body) ' + + `in a multi-line message, but got: ${lines.length}`); + return result; + } if (lines[1].length !== 0) { result.errors.push(`Expected an empty line between the subject and the body, ` + `but got a second line of length: ${lines[1].length}`); @@ -633,6 +638,11 @@ function checkBody(subject, bodyLines) { const errors = []; if (bodyLines.length === 0) { errors.push('At least one line is expected in the body, but got empty body.'); + return errors; + } + if (bodyLines.length === 1 && bodyLines[0].trim() === '') { + errors.push('Unexpected empty body'); + return errors; } for (const [i, line] of bodyLines.entries()) { if (line.length > 72) { @@ -662,23 +672,32 @@ function checkBody(subject, bodyLines) { } const mergeMessageRe = new RegExp("^Merge branch '[^\\000-\\037\\177 ~^:?*[]+' " + 'into [^\\000-\\037\\177 ~^:?*[]+$'); -function check(message, additionalVerbs) { +function check(message, additionalVerbs, allowOneLiners) { const errors = []; if (mergeMessageRe.test(message)) { return errors; } const lines = splitLines(message); - const maybeSubjectBody = splitSubjectBody(lines); - if (maybeSubjectBody.errors.length > 0) { - errors.push(...maybeSubjectBody.errors); + if (lines.length === 0) { + errors.push(`The message is empty.`); + return errors; + } + else if (lines.length === 1 && allowOneLiners) { + errors.push(...checkSubject(lines[0], additionalVerbs)); } else { - if (maybeSubjectBody.subjectBody === undefined) { - throw Error('Unexpected undefined subjectBody'); + const maybeSubjectBody = splitSubjectBody(lines); + if (maybeSubjectBody.errors.length > 0) { + errors.push(...maybeSubjectBody.errors); + } + else { + if (maybeSubjectBody.subjectBody === undefined) { + throw Error('Unexpected undefined subjectBody'); + } + const subjectBody = maybeSubjectBody.subjectBody; + errors.push(...checkSubject(subjectBody.subject, additionalVerbs)); + errors.push(...checkBody(subjectBody.subject, subjectBody.bodyLines)); } - const subjectBody = maybeSubjectBody.subjectBody; - errors.push(...checkSubject(subjectBody.subject, additionalVerbs)); - errors.push(...checkBody(subjectBody.subject, subjectBody.bodyLines)); } // Post-condition for (const error of errors) { @@ -4523,10 +4542,24 @@ function runWithExceptions() { additionalVerbs.add(verb); } } + // Parse allow-one-liners input + const allowOneLinersText = core.getInput('allow-one-liners', { + required: false + }); + const allowOneLiners = !allowOneLinersText + ? false + : input.parseAllowOneLiners(allowOneLinersText); + if (allowOneLiners === null) { + const error = 'Unexpected value for allow-one-liners. ' + + `Expected either 'true' or 'false', got: ${allowOneLinersText}`; + core.error(error); + core.setFailed(error); + return; + } // Parts of the error message to be concatenated with '\n' const parts = []; for (const [messageIndex, message] of messages.entries()) { - const errors = inspection.check(message, additionalVerbs); + const errors = inspection.check(message, additionalVerbs, allowOneLiners); if (errors.length > 0) { const repr = represent.formatErrors(message, messageIndex, errors); parts.push(repr); @@ -9287,7 +9320,7 @@ function getNextPage (octokit, link, headers) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.parseVerbs = void 0; +exports.parseAllowOneLiners = exports.parseVerbs = void 0; function parseVerbs(text) { const lines = text.split('\n'); const verbs = []; @@ -9301,6 +9334,16 @@ function parseVerbs(text) { return verbs; } exports.parseVerbs = parseVerbs; +function parseAllowOneLiners(text) { + if (text === '' || text.toLowerCase() === 'false' || text === '0') { + return false; + } + if (text.toLowerCase() === 'true' || text === '1') { + return true; + } + return null; +} +exports.parseAllowOneLiners = parseAllowOneLiners; /***/ }), diff --git a/local/powershell/OpinionatedCommitMessage.Tests.ps1 b/local/powershell/OpinionatedCommitMessage.Tests.ps1 index 92f0085..be84022 100755 --- a/local/powershell/OpinionatedCommitMessage.Tests.ps1 +++ b/local/powershell/OpinionatedCommitMessage.Tests.ps1 @@ -179,7 +179,7 @@ function TestOKMergeBranch if ($got -ne $expected) { - Write-Host "TestOKMergeBranch: OK" + Write-Host "TestOKMergeBranch: FAILED" WriteExpectedGot -expected $expected -got $got return $false } @@ -245,6 +245,54 @@ function TestOKPathToAdditionalVerbs } } +function TestOKWithAllowOneLiners +{ + $message = "Do something" + $got = (powershell ` + -File OpinionatedCommitMessage.ps1 ` + -message $message ` + -allowOneLiners ` + -dontThrow + )|Out-String + + $nl = [Environment]::NewLine + $expected = "The message is OK.${nl}" + + if ($got -ne $expected) + { + Write-Host "TestOKWithAllowOneLiners: FAILED" + WriteExpectedGot -expected $expected -got $got + return $false + } + + Write-Host "TestOKWithAllowOneLiners: OK" + return $true +} + +function TestFailWithAllowOneLiners +{ + $message = "Do something." + $got = (powershell ` + -File OpinionatedCommitMessage.ps1 ` + -message $message ` + -allowOneLiners ` + -dontThrow + )|Out-String + + $nl = [Environment]::NewLine + $expected = "* The subject must not end with a dot ('.').${nl}" + + if ($got -ne $expected) + { + Write-Host "TestFailWithAllowOneLiners: FAILED" + WriteExpectedGot -expected $expected -got $got + return $false + } + + Write-Host "TestFailWithAllowOneLiners: OK" + return $true +} + function Main { Push-Location @@ -263,6 +311,10 @@ function Main $success = TestOKCarriageReturn -and $success $success = TestOKAdditionalVerbs -and $success $success = TestOKPathToAdditionalVerbs -and $success + $success = TestOKWithAllowOneLiners -and $success + $success = TestFailWithAllowOneLiners -and $success + + # TODO: TestFailAllowOneLiners if(!$success) { diff --git a/local/powershell/OpinionatedCommitMessage.ps1 b/local/powershell/OpinionatedCommitMessage.ps1 index c9f7e5e..1414afc 100644 --- a/local/powershell/OpinionatedCommitMessage.ps1 +++ b/local/powershell/OpinionatedCommitMessage.ps1 @@ -1,6 +1,6 @@ #!/usr/bin/env pwsh # This file was generated by src/scripts/toPowershell.ts. Do NOT edit or append! -# Version: 2.0.0 +# Version: 2.0.0-post1 # Copyright (c) 2020 Marko Ristin # MIT License @@ -17,6 +17,10 @@ param( [string] $pathToAdditionalVerbs = $null, + [Parameter(HelpMessage = "If set, one-liner commit messages are allowed")] + [switch] + $allowOneLiners = $false, + [Parameter(HelpMessage = "If set, the script does not throw an exception on failed checks")] [switch] $dontThrow = $false @@ -870,6 +874,12 @@ function CheckBody([string]$subject, [string[]] $bodyLines) return $errors } + if (($bodyLines.Length -eq 1) -and ($bodyLines[0].Trim -eq "")) + { + $errors += "Unexpected empty body" + return $errors + } + for($i = 0; $i -lt $bodyLines.Count; $i++) { $line = $bodyLines[$i] @@ -912,31 +922,53 @@ function Check([string]$text, [hashtable]$verbs) } $lines = $text -Split "`n" - - if ($lines.Count -lt 3) - { - $errors += "Expected at least three lines (subject, empty, body), but got: $( $lines.Count )" - return $errors - } - $trimmedLines = @() foreach ($line in $lines) { $trimmedLines += $line -Replace '\r$' } - if ($trimmedLines[1] -ne "") + if ($trimmedLines.Count -eq 0) { - $errors += "Expected an empty line between the subject and the body, " + ` - "but got a line: $( $trimmedLines[1]|ConvertTo-Json )" + errors += "The message is empty." return $errors } + elseif (($trimmedLines.Length -eq 1) -and $allowOneLiners) + { + $subject = $trimmedLines[0] + $errors = $errors + ( CheckSubject -subject $subject -verbs $verbs ) + } + else + { + if (($trimmedLines.Length -eq 0) -or ($trimmedLines.Length -eq 1)) + { + $errors += "Expected at least three lines (subject, empty, body), but got: $( $lines.Count )" + return $errors + } + elseif ($trimmedLines.Length -eq 2) + { + $errors += ( + "Expected at least three lines (subject, empty, body) in a multi-line message, " + + "but got: $( $lines.Count )" + ) + return $errors + } + else + { + if ($trimmedLines[1] -ne "") + { + $errors += "Expected an empty line between the subject and the body, " + ` + "but got a line: $( $trimmedLines[1]|ConvertTo-Json )" + return $errors + } - $subject = $trimmedLines[0] - $errors = $errors + ( CheckSubject -subject $subject -verbs $verbs ) + $subject = $trimmedLines[0] + $errors = $errors + ( CheckSubject -subject $subject -verbs $verbs ) - $bodyLines = $trimmedLines |Select-Object -Skip 2 - $errors = $errors + ( CheckBody -subject $subject -bodyLines $bodyLines) + $bodyLines = $trimmedLines |Select-Object -Skip 2 + $errors = $errors + ( CheckBody -subject $subject -bodyLines $bodyLines) + } + } return $errors } diff --git a/local/powershell/OpinionatedCommitMessage.ps1.template b/local/powershell/OpinionatedCommitMessage.ps1.template index 81b066a..7a514e3 100755 --- a/local/powershell/OpinionatedCommitMessage.ps1.template +++ b/local/powershell/OpinionatedCommitMessage.ps1.template @@ -12,6 +12,10 @@ param( [string] $pathToAdditionalVerbs = $null, + [Parameter(HelpMessage = "If set, one-liner commit messages are allowed")] + [switch] + $allowOneLiners = $false, + [Parameter(HelpMessage = "If set, the script does not throw an exception on failed checks")] [switch] $dontThrow = $false @@ -138,6 +142,12 @@ function CheckBody([string]$subject, [string[]] $bodyLines) return $errors } + if (($bodyLines.Length -eq 1) -and ($bodyLines[0].Trim -eq "")) + { + $errors += "Unexpected empty body" + return $errors + } + for($i = 0; $i -lt $bodyLines.Count; $i++) { $line = $bodyLines[$i] @@ -180,31 +190,53 @@ function Check([string]$text, [hashtable]$verbs) } $lines = $text -Split "`n" - - if ($lines.Count -lt 3) - { - $errors += "Expected at least three lines (subject, empty, body), but got: $( $lines.Count )" - return $errors - } - $trimmedLines = @() foreach ($line in $lines) { $trimmedLines += $line -Replace '\r$' } - if ($trimmedLines[1] -ne "") + if ($trimmedLines.Count -eq 0) { - $errors += "Expected an empty line between the subject and the body, " + ` - "but got a line: $( $trimmedLines[1]|ConvertTo-Json )" + errors += "The message is empty." return $errors } + elseif (($trimmedLines.Length -eq 1) -and $allowOneLiners) + { + $subject = $trimmedLines[0] + $errors = $errors + ( CheckSubject -subject $subject -verbs $verbs ) + } + else + { + if (($trimmedLines.Length -eq 0) -or ($trimmedLines.Length -eq 1)) + { + $errors += "Expected at least three lines (subject, empty, body), but got: $( $lines.Count )" + return $errors + } + elseif ($trimmedLines.Length -eq 2) + { + $errors += ( + "Expected at least three lines (subject, empty, body) in a multi-line message, " + + "but got: $( $lines.Count )" + ) + return $errors + } + else + { + if ($trimmedLines[1] -ne "") + { + $errors += "Expected an empty line between the subject and the body, " + ` + "but got a line: $( $trimmedLines[1]|ConvertTo-Json )" + return $errors + } - $subject = $trimmedLines[0] - $errors = $errors + ( CheckSubject -subject $subject -verbs $verbs ) + $subject = $trimmedLines[0] + $errors = $errors + ( CheckSubject -subject $subject -verbs $verbs ) - $bodyLines = $trimmedLines |Select-Object -Skip 2 - $errors = $errors + ( CheckBody -subject $subject -bodyLines $bodyLines) + $bodyLines = $trimmedLines |Select-Object -Skip 2 + $errors = $errors + ( CheckBody -subject $subject -bodyLines $bodyLines) + } + } return $errors } diff --git a/package.json b/package.json index 1a76927..d37bd2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mristin/opinionated-commit-message", - "version": "2.0.0", + "version": "2.0.0-post1", "description": "Github Action to check commit messages according to an opinionated style", "keywords": [ "github", diff --git a/src/__tests__/inspection.test.ts b/src/__tests__/inspection.test.ts index 76cba52..c6289c9 100644 --- a/src/__tests__/inspection.test.ts +++ b/src/__tests__/inspection.test.ts @@ -1,13 +1,31 @@ import * as inspection from '../inspection'; -it('reports no errors on correct message.', () => { +it('reports no errors on correct multi-line message.', () => { 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 errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); + expect(errors).toEqual([]); +}); + +it('reports no errors on OK multi-line message with allowed one-liners.', () => { + 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 errors = inspection.check(message, new Set(), true); + expect(errors).toEqual([]); +}); + +it('reports no errors on OK single-line message with allowed one-liners.', () => { + const message = 'Change SomeClass to OtherClass'; + + const errors = inspection.check(message, new Set(), true); expect(errors).toEqual([]); }); @@ -18,18 +36,33 @@ it('tolerates hash code in the subject.', () => { 'The license files naming was inconsistent (`LICENSE.TXT` and \n' + '`LICENSE.txt`). This makes them all uniform (`LICENSE.txt`).'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([]); }); -it('reports too few lines.', () => { +it('reports too few lines with disallowed one-liners.', () => { const message = 'Change SomeClass to OtherClass'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([ 'Expected at least three lines (subject, empty, body), but got: 1' ]); }); +it('reports missing body with disallowed one-liners.', () => { + const message = 'Change SomeClass to OtherClass\n\n'; + const errors = inspection.check(message, new Set(), false); + expect(errors).toEqual(['Unexpected empty body']); +}); + +it('reports missing body with allowed one-liners.', () => { + const message = 'Change SomeClass to OtherClass\n'; + const errors = inspection.check(message, new Set(), true); + expect(errors).toEqual([ + 'Expected at least three lines (subject, empty, body) ' + + 'in a multi-line message, but got: 2' + ]); +}); + it('reports on missing empty line between subject and body.', () => { const message = 'Change SomeClass to OtherClass\n' + @@ -37,7 +70,7 @@ it('reports on missing empty line between subject and body.', () => { 'This replaces the SomeClass with OtherClass in all of the module \n' + 'since Some class was deprecated.'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([ 'Expected an empty line between the subject and the body, ' + 'but got a second line of length: 3' @@ -51,7 +84,7 @@ it('reports the subject starting with a non-capitalized word.', () => { 'This replaces the SomeClass with OtherClass in all of the module \n' + 'since Some class was deprecated.'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([ 'The subject must start with a capitalized verb (e.g., "Change").' ]); @@ -67,7 +100,7 @@ it( 'This replaces the SomeClass with OtherClass in all of the module \n' + 'since Some class was deprecated.'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors.length).toBe(1); expect(errors[0].startsWith('The subject must start in imperative mood')); } @@ -85,7 +118,8 @@ it( const errors = inspection.check( message, - new Set(['table']) + new Set(['table']), + false ); expect(errors.length).toBe(1); expect(errors[0].startsWith('The subject must start in imperative mood')); @@ -96,7 +130,8 @@ it('accepts the subject starting with an additional verb.', () => { const message = 'Table that for me\n\nThis is a dummy commit.'; const errors = inspection.check( message, - new Set(['table']) + new Set(['table']), + false ); expect(errors).toEqual([]); }); @@ -108,7 +143,14 @@ it('reports the subject ending in a dot.', () => { 'This replaces the SomeClass with OtherClass in all of the module \n' + 'since Some class was deprecated.'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); + expect(errors).toEqual(["The subject must not end with a dot ('.')."]); +}); + +it('reports an incorrect one-line message with allowed one-liners.', () => { + const message = 'Change SomeClass to OtherClass.'; + + const errors = inspection.check(message, new Set(), true); expect(errors).toEqual(["The subject must not end with a dot ('.')."]); }); @@ -119,7 +161,7 @@ it('reports too long a body line.', () => { 'This replaces the SomeClass with OtherClass in all of the module ' + 'since Some class was deprecated.'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([ 'The line 3 of the message (line 1 of the body) exceeds the limit of ' + '72 characters. The line contains 97 characters: ' + @@ -143,7 +185,7 @@ it('accepts a body line of exactly 72 characters.', () => { '1234567890' + '12'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([]); }); @@ -162,14 +204,14 @@ it('ignores the carriage return.', () => { '1234567890' + '12'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([]); }); it('accepts body that does not start with a word.', () => { const message = 'Change SomeClass to OtherClass\n\n* Do something'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([]); }); @@ -179,7 +221,7 @@ it('reports duplicate starting word in subject and body.', () => { '\n' + 'Change SomeClass so that OtherClass does not conflict..'; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([ 'The first word of the subject ("Change") must not match ' + 'the first word of the body.' @@ -189,6 +231,6 @@ it('reports duplicate starting word in subject and body.', () => { it('ignores merge messages.', () => { const message = "Merge branch 'V20DataModel' into miho/Conform-to-spec"; - const errors = inspection.check(message, new Set()); + const errors = inspection.check(message, new Set(), false); expect(errors).toEqual([]); }); diff --git a/src/__tests__/main.test.ts b/src/__tests__/main.test.ts index f25cfd5..8527265 100644 --- a/src/__tests__/main.test.ts +++ b/src/__tests__/main.test.ts @@ -55,6 +55,20 @@ it('considers additional verbs from path.', () => { expect(mockSetFailed.mock.calls).toEqual([]); }); +it('considers allow-one-liners.', () => { + (commitMessages.retrieve as any).mockImplementation(() => ['Do something']); + + const mockSetFailed = jest.fn(); + (core as any).setFailed = mockSetFailed; + + (core as any).getInput = (name: string) => + name === 'allow-one-liners' ? 'true' : null; + + mainImpl.run(); + + expect(mockSetFailed.mock.calls).toEqual([]); +}); + it('formats properly no error message.', () => { (commitMessages.retrieve as any).mockImplementation(() => [ 'Change SomeClass to OtherClass\n' + diff --git a/src/input.ts b/src/input.ts index 6a98ee7..ba98721 100644 --- a/src/input.ts +++ b/src/input.ts @@ -13,3 +13,15 @@ export function parseVerbs(text: string): string[] { return verbs; } + +export function parseAllowOneLiners(text: string): boolean | null { + if (text === '' || text.toLowerCase() === 'false' || text === '0') { + return false; + } + + if (text.toLowerCase() === 'true' || text === '1') { + return true; + } + + return null; +} diff --git a/src/inspection.ts b/src/inspection.ts index 1b691fe..e505d9d 100644 --- a/src/inspection.ts +++ b/src/inspection.ts @@ -22,12 +22,18 @@ function splitLines(message: string): string[] { function splitSubjectBody(lines: string[]): MaybeSubjectBody { const result: MaybeSubjectBody = {errors: []}; - if (lines.length < 3) { + if (lines.length === 0 || lines.length === 1) { result.errors.push( - `Expected at least three lines (subject, empty, body), ` + + 'Expected at least three lines (subject, empty, body), ' + `but got: ${lines.length}` ); return result; + } else if (lines.length === 2) { + result.errors.push( + 'Expected at least three lines (subject, empty, body) ' + + `in a multi-line message, but got: ${lines.length}` + ); + return result; } if (lines[1].length !== 0) { @@ -130,6 +136,12 @@ function checkBody(subject: string, bodyLines: string[]): string[] { errors.push( 'At least one line is expected in the body, but got empty body.' ); + return errors; + } + + if (bodyLines.length === 1 && bodyLines[0].trim() === '') { + errors.push('Unexpected empty body'); + return errors; } for (const [i, line] of bodyLines.entries()) { @@ -174,7 +186,11 @@ const mergeMessageRe = new RegExp( 'into [^\\000-\\037\\177 ~^:?*[]+$' ); -export function check(message: string, additionalVerbs: Set): string[] { +export function check( + message: string, + additionalVerbs: Set, + allowOneLiners: boolean +): string[] { const errors: string[] = []; if (mergeMessageRe.test(message)) { @@ -183,17 +199,25 @@ export function check(message: string, additionalVerbs: Set): string[] { const lines = splitLines(message); - const maybeSubjectBody = splitSubjectBody(lines); - if (maybeSubjectBody.errors.length > 0) { - errors.push(...maybeSubjectBody.errors); + if (lines.length === 0) { + errors.push(`The message is empty.`); + return errors; + } else if (lines.length === 1 && allowOneLiners) { + errors.push(...checkSubject(lines[0], additionalVerbs)); } else { - if (maybeSubjectBody.subjectBody === undefined) { - throw Error('Unexpected undefined subjectBody'); - } - const subjectBody = maybeSubjectBody.subjectBody; + const maybeSubjectBody = splitSubjectBody(lines); + if (maybeSubjectBody.errors.length > 0) { + errors.push(...maybeSubjectBody.errors); + } else { + if (maybeSubjectBody.subjectBody === undefined) { + throw Error('Unexpected undefined subjectBody'); + } + const subjectBody = maybeSubjectBody.subjectBody; + + errors.push(...checkSubject(subjectBody.subject, additionalVerbs)); - errors.push(...checkSubject(subjectBody.subject, additionalVerbs)); - errors.push(...checkBody(subjectBody.subject, subjectBody.bodyLines)); + errors.push(...checkBody(subjectBody.subject, subjectBody.bodyLines)); + } } // Post-condition diff --git a/src/mainImpl.ts b/src/mainImpl.ts index 3456d1a..c1effc9 100644 --- a/src/mainImpl.ts +++ b/src/mainImpl.ts @@ -47,11 +47,29 @@ function runWithExceptions(): void { } } + // Parse allow-one-liners input + const allowOneLinersText = core.getInput('allow-one-liners', { + required: false + }); + + const allowOneLiners: boolean | null = !allowOneLinersText + ? false + : input.parseAllowOneLiners(allowOneLinersText); + + if (allowOneLiners === null) { + const error = + 'Unexpected value for allow-one-liners. ' + + `Expected either 'true' or 'false', got: ${allowOneLinersText}`; + core.error(error); + core.setFailed(error); + return; + } + // Parts of the error message to be concatenated with '\n' const parts: string[] = []; for (const [messageIndex, message] of messages.entries()) { - const errors = inspection.check(message, additionalVerbs); + const errors = inspection.check(message, additionalVerbs, allowOneLiners); if (errors.length > 0) { const repr: string = represent.formatErrors(