Skip to content

Commit

Permalink
feat: Add disallowScopes option (#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmosawy authored May 4, 2022
1 parent bc0a635 commit 6a7ed2d
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/lint-pr-title-preview.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: 'Lint PR title preview (current branch)'
name: "Lint PR title preview (current branch)"
on:
pull_request:
types:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ The action works without configuration, however you can provide options for cust
ui
# Configure that a scope must always be provided.
requireScope: true
# Configure which scopes are disallowed in PR titles. For instance, by setting
# the value below, `chore(release): ...` or `ci(e2e,release): ...` will be rejected.
disallowScopes: |
release
# 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]).+$
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
disallowScopes:
description: 'Configure which scopes are disallowed in PR titles.'
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
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module.exports = async function run() {
types,
scopes,
requireScope,
disallowScopes,
wip,
subjectPattern,
subjectPatternError,
Expand Down Expand Up @@ -67,6 +68,7 @@ module.exports = async function run() {
types,
scopes,
requireScope,
disallowScopes,
subjectPattern,
subjectPatternError,
headerPattern,
Expand Down Expand Up @@ -108,6 +110,7 @@ module.exports = async function run() {
types,
scopes,
requireScope,
disallowScopes,
subjectPattern,
subjectPatternError,
headerPattern,
Expand Down
6 changes: 6 additions & 0 deletions src/parseConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ module.exports = function parseConfig() {
requireScope = ConfigParser.parseBoolean(process.env.INPUT_REQUIRESCOPE);
}

let disallowScopes;
if (process.env.INPUT_DISALLOWSCOPES) {
disallowScopes = ConfigParser.parseEnum(process.env.INPUT_DISALLOWSCOPES);
}

let subjectPattern;
if (process.env.INPUT_SUBJECTPATTERN) {
subjectPattern = ConfigParser.parseString(process.env.INPUT_SUBJECTPATTERN);
Expand Down Expand Up @@ -73,6 +78,7 @@ module.exports = function parseConfig() {
types,
scopes,
requireScope,
disallowScopes,
wip,
subjectPattern,
subjectPatternError,
Expand Down
17 changes: 17 additions & 0 deletions src/validatePrTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = async function validatePrTitle(
types,
scopes,
requireScope,
disallowScopes,
subjectPattern,
subjectPatternError,
headerPattern,
Expand Down Expand Up @@ -46,6 +47,10 @@ module.exports = async function validatePrTitle(
return scopes && !scopes.includes(s);
}

function isDisallowedScope(s) {
return disallowScopes && disallowScopes.includes(s);
}

if (!result.type) {
throw new Error(
`No release type found in pull request title "${prTitle}". Add a prefix to indicate what kind of release this pull request corresponds to. For reference, see https://www.conventionalcommits.org/\n\n${printAvailableTypes()}`
Expand Down Expand Up @@ -76,6 +81,7 @@ module.exports = async function validatePrTitle(
const givenScopes = result.scope
? result.scope.split(',').map((scope) => scope.trim())
: undefined;

const unknownScopes = givenScopes ? givenScopes.filter(isUnknownScope) : [];
if (scopes && unknownScopes.length > 0) {
throw new Error(
Expand All @@ -89,6 +95,17 @@ module.exports = async function validatePrTitle(
);
}

const disallowedScopes = givenScopes
? givenScopes.filter(isDisallowedScope)
: [];
if (disallowScopes && disallowedScopes.length > 0) {
throw new Error(
`Disallowed ${
disallowedScopes.length === 1 ? 'scope was' : 'scopes were'
} found: ${disallowScopes.join(', ')}`
);
}

function throwSubjectPatternError(message) {
if (subjectPatternError) {
message = formatMessage(subjectPatternError, {
Expand Down
62 changes: 62 additions & 0 deletions src/validatePrTitle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,68 @@ describe('defined scopes', () => {
});
});

describe('disallow scopes', () => {
it('passes when a single scope is provided, but not present in disallowScopes with one item', async () => {
await validatePrTitle('fix(core): Bar', {disallowScopes: ['release']});
});

it('passes when multiple scopes are provided, but not present in disallowScopes with one item', async () => {
await validatePrTitle('fix(core,e2e,bar): Bar', {
disallowScopes: ['release']
});
});

it('passes when a single scope is provided, but not present in disallowScopes with multiple items', async () => {
await validatePrTitle('fix(core): Bar', {
disallowScopes: ['release', 'test']
});
});

it('passes when multiple scopes are provided, but not present in disallowScopes with multiple items', async () => {
await validatePrTitle('fix(core,e2e,bar): Bar', {
disallowScopes: ['release', 'test']
});
});

it('throws when a single scope is provided and it is present in disallowScopes with one item', async () => {
await expect(
validatePrTitle('fix(release): Bar', {disallowScopes: ['release']})
).rejects.toThrow('Disallowed scope was found: release');
});

it('throws when a single scope is provided and it is present in disallowScopes with multiple item', async () => {
await expect(
validatePrTitle('fix(release): Bar', {
disallowScopes: ['release', 'test']
})
).rejects.toThrow('Disallowed scope was found: release');
});

it('throws when multiple scopes are provided and one of them is present in disallowScopes with one item ', async () => {
await expect(
validatePrTitle('fix(release,e2e): Bar', {
disallowScopes: ['release']
})
).rejects.toThrow('Disallowed scope was found: release');
});

it('throws when multiple scopes are provided and one of them is present in disallowScopes with multiple items ', async () => {
await expect(
validatePrTitle('fix(release,e2e): Bar', {
disallowScopes: ['release', 'test']
})
).rejects.toThrow('Disallowed scope was found: release');
});

it('throws when multiple scopes are provided and more than one of them are present in disallowScopes', async () => {
await expect(
validatePrTitle('fix(release,test): Bar', {
disallowScopes: ['release', 'test']
})
).rejects.toThrow('Disallowed scopes were found: release, test');
});
});

describe('scope allowlist not defined', () => {
it('passes when a scope is provided', async () => {
await validatePrTitle('fix(core): Bar', {
Expand Down

0 comments on commit 6a7ed2d

Please sign in to comment.