From 0696d28db546530fa4b0a17d34d67ab02b666ccc Mon Sep 17 00:00:00 2001 From: Ashley Frieze Date: Wed, 17 Sep 2025 16:36:25 +0100 Subject: [PATCH 1/5] Add `none` option for inverse matching the changes to a label --- README.md | 11 +++++++-- __tests__/fixtures/all_options.yml | 5 ++++ __tests__/labeler.test.ts | 37 ++++++++++++++++++++++++++++++ src/api/get-label-configs.ts | 5 ++-- src/labeler.ts | 6 +++++ 5 files changed, 60 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3816b6989..9c1f6593c 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 both 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..9b1636a74 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..1d9707aba 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 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 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; } From c0bf71b2797b1c2462d5833f41f574215318afc7 Mon Sep 17 00:00:00 2001 From: Ashley Frieze Date: Wed, 17 Sep 2025 17:48:46 +0100 Subject: [PATCH 2/5] Update __tests__/labeler.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- __tests__/labeler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index 1d9707aba..c091da9b7 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -158,7 +158,7 @@ describe('checkMatchConfigs', () => { expect(result).toBe(true); }); - it('returns false when when files match the "none" config', () => { + it('returns false when files match the "none" config', () => { const matchConfig: MatchConfig[] = [ { none: [ From 85dc55603e236087f011b3bc185d006006ef0a03 Mon Sep 17 00:00:00 2001 From: Ashley Frieze Date: Wed, 17 Sep 2025 17:49:03 +0100 Subject: [PATCH 3/5] Update __tests__/fixtures/all_options.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- __tests__/fixtures/all_options.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/fixtures/all_options.yml b/__tests__/fixtures/all_options.yml index 9b1636a74..69719b0d5 100644 --- a/__tests__/fixtures/all_options.yml +++ b/__tests__/fixtures/all_options.yml @@ -13,7 +13,7 @@ label1: - changed-files: - all-globs-to-all-files: ['notthisglob'] - head-branch: ['notthisone'] - - base-branch: ['notthisone'] + - base-branch: ['notthisone'] label2: - changed-files: From 4f06a8f5cc1bbe4f765a8bce6adbf0bcbc0d8e69 Mon Sep 17 00:00:00 2001 From: Ashley Frieze Date: Wed, 17 Sep 2025 17:49:14 +0100 Subject: [PATCH 4/5] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c1f6593c..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 three top-level keys, `any`, `all` and `none`, 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: From dc14813acde7198ab9c285a3b2b1a84a0912725c Mon Sep 17 00:00:00 2001 From: Ashley Frieze Date: Wed, 17 Sep 2025 20:14:03 +0100 Subject: [PATCH 5/5] Update __tests__/labeler.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- __tests__/labeler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/labeler.test.ts b/__tests__/labeler.test.ts index c091da9b7..1c20fe76e 100644 --- a/__tests__/labeler.test.ts +++ b/__tests__/labeler.test.ts @@ -143,7 +143,7 @@ describe('checkMatchConfigs', () => { expect(result).toBeTruthy(); }); - it('returns true when when no files match the "none" config', () => { + it('returns true when no files match the "none" config', () => { const matchConfig: MatchConfig[] = [ { none: [