diff --git a/README.md b/README.md index 3816b6989..6f2849401 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ The base match object is defined as: - head-branch: ['list', 'of', 'regexps'] ``` -There are two top-level keys, `any` and `all`, which both accept the same configuration options: +There are three top-level keys, `any`, `all` and `none`, which all accept the same configuration options: ```yml - any: - changed-files: @@ -66,12 +66,13 @@ There are two top-level keys, `any` and `all`, which both accept the same config - head-branch: ['list', 'of', 'regexps'] ``` -From a boolean logic perspective, top-level match objects, and options within `all` are `AND`-ed together and individual match rules within the `any` object are `OR`-ed. +From a boolean logic perspective, top-level match objects, and options within `all` are `AND`-ed together and individual match rules within the `any` object are `OR`-ed. `none` is the inverse of `any`. One or all fields can be provided for fine-grained matching. The fields are defined as follows: - `all`: ALL of the provided options must match for the label to be applied - `any`: if ANY of the provided options match then the label will be applied +- `none`: will assign the label if none of the provided options match - e.g. missing changes to documentation or unit tests - `base-branch`: match regexps against the base branch name - `head-branch`: match regexps against the head branch name - `changed-files`: match glob patterns against the changed paths @@ -137,6 +138,12 @@ Documentation: - changed-files: - any-glob-to-any-file: '**/*.md' +# Add `Missing Documentation` label to any code change which doesn't include doc changes +Missing Documentation: +- none: + - changed-files: + - any-glob-to-any-file: '**/*.md' + # Add 'source' label to any change to src files within the source dir EXCEPT for the docs sub-folder source: - all: diff --git a/__tests__/fixtures/all_options.yml b/__tests__/fixtures/all_options.yml index 9417d13ce..69719b0d5 100644 --- a/__tests__/fixtures/all_options.yml +++ b/__tests__/fixtures/all_options.yml @@ -9,6 +9,11 @@ label1: - all-globs-to-all-files: ['glob'] - head-branch: ['regexp'] - base-branch: ['regexp'] + - none: + - changed-files: + - all-globs-to-all-files: ['notthisglob'] + - head-branch: ['notthisone'] + - base-branch: ['notthisone'] label2: - changed-files: diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index b780c82ff..1c20fe76e 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -40,6 +40,13 @@ describe('getLabelConfigMapFromObject', () => { {baseBranch: undefined, headBranch: ['regexp']}, {baseBranch: ['regexp'], headBranch: undefined} ] + }, + { + none: [ + {changedFiles: [{allGlobsToAllFiles: ['notthisglob']}]}, + {baseBranch: undefined, headBranch: ['notthisone']}, + {baseBranch: ['notthisone'], headBranch: undefined} + ] } ]); expected.set('label2', [ @@ -135,6 +142,36 @@ describe('checkMatchConfigs', () => { expect(result).toBeTruthy(); }); + + it('returns true when no files match the "none" config', () => { + const matchConfig: MatchConfig[] = [ + { + none: [ + {changedFiles: [{anyGlobToAnyFile: ['*.md']}]}, + {headBranch: ['some-branch']} + ] + } + ]; + const changedFiles = ['foo.txt', 'bar.txt']; + + const result = checkMatchConfigs(changedFiles, matchConfig, false); + expect(result).toBe(true); + }); + + it('returns false when files match the "none" config', () => { + const matchConfig: MatchConfig[] = [ + { + none: [ + {changedFiles: [{anyGlobToAnyFile: ['*.md']}]}, + {headBranch: ['some-branch']} + ] + } + ]; + const changedFiles = ['foo.md', 'bar.md']; + + const result = checkMatchConfigs(changedFiles, matchConfig, false); + expect(result).toBe(false); + }); }); describe('when multiple MatchConfigs are supplied', () => { diff --git a/src/api/get-label-configs.ts b/src/api/get-label-configs.ts index 4db33f28e..6722d116c 100644 --- a/src/api/get-label-configs.ts +++ b/src/api/get-label-configs.ts @@ -14,6 +14,7 @@ import {toBranchMatchConfig, BranchMatchConfig} from '../branch'; export interface MatchConfig { all?: BaseMatchConfig[]; any?: BaseMatchConfig[]; + none?: BaseMatchConfig[]; } export type BaseMatchConfig = BranchMatchConfig & ChangedFilesMatchConfig; @@ -79,9 +80,9 @@ export function getLabelConfigMapFromObject( } Object.entries(configValue).forEach(([key, value]) => { - // If the top level `any` or `all` keys are provided then set them, and convert their values to + // If the top level `any`, `all` or `none` keys are provided then set them, and convert their values to // our config objects. - if (key === 'any' || key === 'all') { + if (key === 'any' || key === 'all' || key === 'none') { if (Array.isArray(value)) { const newConfigs = value.map(toMatchConfig); updatedConfig.push({[key]: newConfigs}); diff --git a/src/labeler.ts b/src/labeler.ts index 816544390..d39a5be35 100644 --- a/src/labeler.ts +++ b/src/labeler.ts @@ -134,6 +134,12 @@ function checkMatch( } } + if (matchConfig.none) { + if (checkAny(matchConfig.none, changedFiles, dot)) { + return false; + } + } + return true; }