Skip to content

Commit

Permalink
feat: Add regex validation for subject (#71)
Browse files Browse the repository at this point in the history
* feat: Add regex validation for subject

* Update yml

* Remove config

* Reenable

* Add logging

* Fix assignment

* Remove config
  • Loading branch information
amannn authored Jan 11, 2021
1 parent 21d965a commit 04b071e
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 5 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v2.1.0
- uses: amannn/action-semantic-pull-request@v3.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Optionally, you can provide options for further constraints.
Expand All @@ -48,6 +48,9 @@ jobs:
ui
# Configure that a scope must always be provided.
requireScope: true
# Configure additional validation for the subject based on a regex.
# This example ensures the subject doesn't start with an uppercase character.
subjectPattern: ^(?![A-Z]).+$
# For work-in-progress PRs you can typically use draft pull requests
# from Github. However, private repositories on the free plan don't have
# this option and therefore this action allows you to opt-in to using the
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ inputs:
requireScope:
description: "Configure that a scope must always be provided."
required: false
subjectPattern:
description: "Configure additional validation for the subject based on a regex. E.g. '^(?![A-Z]).+$' ensures the subject doesn't start with an uppercase character."
required: false
wip:
description: "For work-in-progress PRs you can typically use draft pull requests from Github. However, private repositories on the free plan don't have this option and therefore this action allows you to opt-in to using the special '[WIP]' prefix to indicate this state. This will avoid the validation of the PR title and the pull request checks remain pending. Note that a second check will be reported if this is enabled."
required: false
4 changes: 4 additions & 0 deletions src/ConfigParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ module.exports = {

parseBoolean(input) {
return JSON.parse(input.trim());
},

parseString(input) {
return input;
}
};
9 changes: 7 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const validatePrTitle = require('./validatePrTitle');
module.exports = async function run() {
try {
const client = github.getOctokit(process.env.GITHUB_TOKEN);
const {types, scopes, requireScope, wip} = parseConfig();
const {types, scopes, requireScope, wip, subjectPattern} = parseConfig();

const contextPullRequest = github.context.payload.pull_request;
if (!contextPullRequest) {
Expand Down Expand Up @@ -34,7 +34,12 @@ module.exports = async function run() {
let validationError;
if (!isWip) {
try {
await validatePrTitle(pullRequest.title, {types, scopes, requireScope});
await validatePrTitle(pullRequest.title, {
types,
scopes,
requireScope,
subjectPattern
});
} catch (error) {
validationError = error;
}
Expand Down
7 changes: 6 additions & 1 deletion src/parseConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ module.exports = function parseConfig() {
requireScope = ConfigParser.parseBoolean(process.env.INPUT_REQUIRESCOPE);
}

let subjectPattern;
if (process.env.INPUT_SUBJECTPATTERN) {
subjectPattern = ConfigParser.parseString(process.env.INPUT_SUBJECTPATTERN);
}

let wip;
if (process.env.INPUT_WIP) {
wip = ConfigParser.parseBoolean(process.env.INPUT_WIP);
}

return {types, scopes, requireScope, wip};
return {types, scopes, requireScope, wip, subjectPattern};
};
23 changes: 22 additions & 1 deletion src/validatePrTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const defaultTypes = Object.keys(conventionalCommitTypes.types);

module.exports = async function validatePrTitle(
prTitle,
{types, scopes, requireScope} = {}
{types, scopes, requireScope, subjectPattern} = {}
) {
if (!types) types = defaultTypes;

Expand All @@ -33,6 +33,10 @@ module.exports = async function validatePrTitle(
);
}

if (!result.subject) {
throw new Error(`No subject found in pull request title "${prTitle}".`);
}

if (!types.includes(result.type)) {
throw new Error(
`Unknown release type "${
Expand All @@ -58,4 +62,21 @@ module.exports = async function validatePrTitle(
)}.`
);
}

if (subjectPattern) {
const match = result.subject.match(new RegExp(subjectPattern));

if (!match) {
throw new Error(
`The subject "${result.subject}" found in pull request title "${prTitle}" doesn't match the configured pattern "${subjectPattern}".`
);
}

const matchedPart = match[0];
if (matchedPart.length !== result.subject.length) {
throw new Error(
`The subject "${result.subject}" found in pull request title "${prTitle}" isn't an exact match for the configured pattern "${subjectPattern}". Please provide a subject that matches the whole pattern exactly.`
);
}
}
};
44 changes: 44 additions & 0 deletions src/validatePrTitle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ it('throws for PR titles without a type', async () => {
);
});

it('throws for PR titles with only a type', async () => {
await expect(validatePrTitle('fix:')).rejects.toThrow(
'No release type found in pull request title "fix:".'
);
});

it('throws for PR titles without a subject', async () => {
await expect(validatePrTitle('fix: ')).rejects.toThrow(
'No subject found in pull request title "fix: ".'
);
});

it('throws for PR titles with an unknown type', async () => {
await expect(validatePrTitle('foo: Bar')).rejects.toThrow(
'Unknown release type "foo" found in pull request title "foo: Bar".'
Expand Down Expand Up @@ -90,3 +102,35 @@ describe('custom types', () => {
);
});
});

describe('description validation', () => {
it('does not validate the description by default', async () => {
await validatePrTitle('fix: sK!"§4123');
});

it('throws for invalid subjects', async () => {
await expect(
validatePrTitle('fix: Foobar', {
subjectPattern: '^(?![A-Z]).+$'
})
).rejects.toThrow(
'The subject "Foobar" found in pull request title "fix: Foobar" doesn\'t match the configured pattern "^(?![A-Z]).+$".'
);
});

it('throws for only partial matches', async () => {
await expect(
validatePrTitle('fix: Foobar', {
subjectPattern: 'Foo'
})
).rejects.toThrow(
'The subject "Foobar" found in pull request title "fix: Foobar" isn\'t an exact match for the configured pattern "Foo". Please provide a subject that matches the whole pattern exactly.'
);
});

it('accepts valid subjects', async () => {
await validatePrTitle('fix: foobar', {
subjectPattern: '^(?![A-Z]).+$'
});
});
});

0 comments on commit 04b071e

Please sign in to comment.