diff --git a/.github/nitpicks.yml b/.github/nitpicks.yml index b9deea9..99dadfc 100644 --- a/.github/nitpicks.yml +++ b/.github/nitpicks.yml @@ -1,15 +1,22 @@ - markdown: | ## Rockstar alert Thanks for this contribution, we _really_ appreciate your help :heart:! + +- markdown: | + Are you _sure_ you want to be console logging or debuggering here?? :trollface: pathFilter: - - '*' + - 'src/**' + contentFilter: + - '(\+\s*console.log\(.*\))' + - '(\+\s*debugger)' + - markdown: | Please proofread any changes to documentation. pathFilter: - '**/*.md' + - markdown: | - ## Uh oh - Don't check in binaries...we use GitHub package registry for that! + Uhh... don't check in binaries here - we use GitHub package registry for that! blocking: true pathFilter: - '+**/*.dll' diff --git a/README.md b/README.md index ba0a114..cc4cd8d 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,21 @@ Add a new YAML file that specifies the comments to be evaluated during PRs. We r - markdown: | ## Rockstar alert Thanks for this contribution, we _really_ appreciate your help :heart:! - pathFilter: - - '*' + - markdown: | ## Uh oh Don't check in binaries...we use GitHub Packages for those! blocking: true pathFilter: - '+**/*.dll' + +- markdown: | + Are you _sure_ you want to be console logging or debuggering here?? :trollface: + pathFilter: + - 'src/**' + contentFilter: + - '(\+\s*console.log\(.*\))' + - '(\+\s*debugger)' ``` Add the Nitpicker action to your workflow and allow access to the `secrets.GITHUB_TOKEN` variable. @@ -69,6 +76,8 @@ In order for this PR to be completed, the author of the PR should address any co Path filters define which comments to add to a PR based on the set of files changed. These can either be explicit file paths or a file path pattern. If you want to add multiple path filters to a comment, they must be separated with a `,` or `;`. Comments will only be added to a PR if it changes files that match the path filter. +If no value for `pathFilter` is specified, `*`, is used. This effectively means any file path will match the comment. + Paths prefixed with `!` are excluded. For example, `!tests/**` will exclude files changed within the `tests/` directory. Paths prefixed with `+` indicate only _new_ files will match. For example, `+depot/*.dll` will match when a file with the file extension `.dll` is added to the `depot/` directory but would not match when an existing `.dll` is edited or deleted in the `depot/` directory. @@ -77,6 +86,23 @@ Paths prefixed with `-` indicate only _removed_ files will match. For example, ` Paths prefixed with `~` indicate only _edited_ files will match. For example, `~sources/*.nupkg` will match when an existing file with the file extension `.nupkg` is edited in the `sources/` directory, but would not match when a `.nugpkg` file is added or deleted in the `sources/` directory. +## Content filters + +Content filters define which comments to add based on the `patch` of the files changed. + +A comment's `contentFilter` parameter accepts an array of regular expressions that are used to match comments based on the changes to a file in a PR. These can be used in combination with path filters to create complex comment rules. For example, this rule will match any typescript file in the `src` directory, except `src/models/debug.ts`, that add a `console.log` or `debugger` statement. + +```yaml +- markdown: | + Did you _really_ mean to commit this debug statement?? + pathFilter: + - 'src/**/*.ts' + - '!src/models/debug.ts' + contentFilter: + - '(\+\s*console.log\(.*\))' + - '(\+\s*debugger)' +``` + ## Development Install the dependencies diff --git a/__tests__/configReader.test.ts b/__tests__/configReader.test.ts index bd93484..7c25f38 100644 --- a/__tests__/configReader.test.ts +++ b/__tests__/configReader.test.ts @@ -3,16 +3,34 @@ import { getConfiguredComments } from '../src/services'; import { Constants } from '../src/constants'; test('read valid config file', () => { - const configFile = './data/valid-nitpicks.yml'; + const configFile = './data/valid_nitpicks.yml'; const inputPath = path.join(__dirname, configFile); const comments = getConfiguredComments(inputPath); - expect(comments.length).toEqual(3); + expect(comments.length).toEqual(5); +}); + +test("pathFilter defaults to '*'", () => { + const configFile = './data/default_path_filter.yml'; + const inputPath = path.join(__dirname, configFile); + + const comments = getConfiguredComments(inputPath); + + expect(comments.every(c => c.pathFilter.every(p => p === '*'))).toBeTruthy(); +}); + +test('contentFilter defaults to undefined', () => { + const configFile = './data/default_content_filter.yml'; + const inputPath = path.join(__dirname, configFile); + + const comments = getConfiguredComments(inputPath); + + expect(comments.every(c => !c.contentFilter)).toBeTruthy(); }); test('read invalid config file', () => { - const configFile = './data/invalid-nitpicks.yml'; + const configFile = './data/invalid_nitpicks.yml'; const inputPath = path.join(__dirname, configFile); const comments = getConfiguredComments(inputPath); @@ -21,7 +39,7 @@ test('read invalid config file', () => { }); test('blocking comment text', () => { - const configFile = './data/blocking-nitpicks.yml'; + const configFile = './data/blocking_nitpicks.yml'; const inputPath = path.join(__dirname, configFile); const comments = getConfiguredComments(inputPath); @@ -33,3 +51,12 @@ test('blocking comment text', () => { } } }); + +test('file doesnt exist', () => { + const configFile = './data/foo_bar.yml'; + const inputPath = path.join(__dirname, configFile); + + expect(() => getConfiguredComments(inputPath)).toThrowError( + 'Nitpicks file does not exist: ' + ); +}); diff --git a/__tests__/data/blocking-nitpicks.yml b/__tests__/data/blocking_nitpicks.yml similarity index 81% rename from __tests__/data/blocking-nitpicks.yml rename to __tests__/data/blocking_nitpicks.yml index b395135..8a43bc3 100644 --- a/__tests__/data/blocking-nitpicks.yml +++ b/__tests__/data/blocking_nitpicks.yml @@ -1,5 +1,5 @@ - markdown: | - This is a blcoking comment + This is a blocking comment blocking: true pathFilter: - '**/*.dll' diff --git a/__tests__/data/default_content_filter.yml b/__tests__/data/default_content_filter.yml new file mode 100644 index 0000000..0a73f71 --- /dev/null +++ b/__tests__/data/default_content_filter.yml @@ -0,0 +1,9 @@ +- markdown: | + ## Notice + Thanks for this contribution. We _really_ appreciate your help! + +- markdown: | + ## Blocking issue + blocking: true + pathFilter: + - '**/*.dll' diff --git a/__tests__/data/default_path_filter.yml b/__tests__/data/default_path_filter.yml new file mode 100644 index 0000000..fc6f337 --- /dev/null +++ b/__tests__/data/default_path_filter.yml @@ -0,0 +1,8 @@ +- markdown: | + ## Notice + Thanks for this contribution. We _really_ appreciate your help! + +- markdown: | + Properly escaped regexp + contentFilter: + - '(\+\s*console.log\(.*\))' diff --git a/__tests__/data/invalid-nitpicks.yml b/__tests__/data/invalid_nitpicks.yml similarity index 100% rename from __tests__/data/invalid-nitpicks.yml rename to __tests__/data/invalid_nitpicks.yml diff --git a/__tests__/data/valid-nitpicks.yml b/__tests__/data/valid-nitpicks.yml deleted file mode 100644 index f35675e..0000000 --- a/__tests__/data/valid-nitpicks.yml +++ /dev/null @@ -1,21 +0,0 @@ -- markdown: | - ## Notice - Thanks for this contribution. We _really_ appreciate your help! - pathFilter: - - '**' -- markdown: | - ## Blocking issue - We no longer check in binaries to this repo - our new convention is to use NuGet packages. - - Read more about this change [here](https://www.nuget.org/). - blocking: true - pathFilter: - - '**/*.dll' -- markdown: | - ## Information - You modified a file that controls the payment processing for the company. Please ensure that you have completed all of the following: - - [ ] Added unit tests to verify changes - - [ ] Tested change in multiple web browsers (Chrome, Firefox, Safari, and Edge) - - [ ] Updated documentation with any API changes - pathFilter: - - 'payments/**' diff --git a/__tests__/data/valid_nitpicks.yml b/__tests__/data/valid_nitpicks.yml new file mode 100644 index 0000000..feb4861 --- /dev/null +++ b/__tests__/data/valid_nitpicks.yml @@ -0,0 +1,30 @@ +# Test that pathFilter alone works +- markdown: | + ## Notice + Thanks for this contribution. We _really_ appreciate your help! + pathFilter: + - '**' +# blocking is a valid option +- markdown: | + ## Blocking issue + We no longer check in binaries to this repo - our new convention is to use NuGet packages. + + Read more about this change [here](https://www.nuget.org/). + blocking: true + pathFilter: + - '**/*.dll' +# Path filter should default to * +- markdown: | + Apply to everything +# Test that contentFilter is a valid option +- markdown: | + Don't add email addresses! + contentFilter: + - '[\w-]+@([\w-]+\.)+[\w-]+' +# Kitchen sink +- markdown: | + Oops - looks like you added a `puts` statement + pathFilter: + - app/**/*.rb + contentFilter: + - '(puts\W+)' diff --git a/__tests__/services/state.test.ts b/__tests__/services/state.test.ts index dafa2cf..81cd82e 100644 --- a/__tests__/services/state.test.ts +++ b/__tests__/services/state.test.ts @@ -1,7 +1,10 @@ -import { getMatchingFilePaths } from '../../src/services'; +import { + getMatchingFilePaths, + getMatchingContentChanges +} from '../../src/services'; import { Comment, Change, ChangeType } from '../../src/models'; -test('* matches everything', () => { +test('getMatchingFilePaths * matches everything', () => { const comment: Comment = { pathFilter: ['*'], markdown: 'Woohoo!', @@ -11,16 +14,17 @@ test('* matches everything', () => { const changes: Change[] = [ { file: 'app/models/foo.rb', - changeType: ChangeType.edit + changeType: ChangeType.edit, + patch: '' } ]; const result = getMatchingFilePaths(comment, changes); - expect(result).toEqual(changes.map(c => c.file)); + expect(result).toEqual(changes); }); -test('match recursively', () => { +test('getMatchingFilePaths matches recursively', () => { const comment: Comment = { pathFilter: ['app/**'], markdown: 'Woohoo!', @@ -30,16 +34,17 @@ test('match recursively', () => { const changes: Change[] = [ { file: 'app/models/foo.rb', - changeType: ChangeType.edit + changeType: ChangeType.edit, + patch: '' } ]; const result = getMatchingFilePaths(comment, changes); - expect(result).toEqual(changes.map(c => c.file)); + expect(result).toEqual(changes); }); -test('ignore case', () => { +test('getMatchingFilePaths ignores case', () => { const comment: Comment = { pathFilter: ['app/**'], markdown: 'Woohoo!', @@ -49,16 +54,17 @@ test('ignore case', () => { const changes: Change[] = [ { file: 'APP/MODELS/FOO.rb', - changeType: ChangeType.edit + changeType: ChangeType.edit, + patch: '' } ]; const result = getMatchingFilePaths(comment, changes); - expect(result).toEqual(changes.map(c => c.file)); + expect(result).toEqual(changes); }); -test('remove exclusion patterns', () => { +test('getMatchingFilePaths removes exclusion patterns', () => { const comment: Comment = { pathFilter: ['app/**', '!app/models/*.h'], markdown: 'Everything except headers', @@ -68,16 +74,17 @@ test('remove exclusion patterns', () => { const changes: Change[] = [ { file: 'app/models/main.h', - changeType: ChangeType.edit + changeType: ChangeType.edit, + patch: '' } ]; const result = getMatchingFilePaths(comment, changes); - expect(result).toEqual([]); + expect(result).toHaveLength(0); }); -test('match new files only', () => { +test('getMatchingFilePaths can match new files only', () => { const comment: Comment = { pathFilter: ['+app/**'], markdown: 'Only new files', @@ -87,20 +94,22 @@ test('match new files only', () => { const changes: Change[] = [ { file: 'app/models/old.rb', - changeType: ChangeType.edit + changeType: ChangeType.edit, + patch: '' }, { file: 'app/models/new.rb', - changeType: ChangeType.add + changeType: ChangeType.add, + patch: '' } ]; const result = getMatchingFilePaths(comment, changes); - expect(result).toEqual(['app/models/new.rb']); + expect(result).toEqual(changes.filter(c => c.changeType === ChangeType.add)); }); -test('match deleted files only', () => { +test('getMatchingFilePaths can match deleted files only', () => { const comment: Comment = { pathFilter: ['-app/**'], markdown: 'Only deleted files', @@ -110,20 +119,24 @@ test('match deleted files only', () => { const changes: Change[] = [ { file: 'app/models/old.rb', - changeType: ChangeType.edit + changeType: ChangeType.edit, + patch: '' }, { file: 'app/models/deleted.rb', - changeType: ChangeType.delete + changeType: ChangeType.delete, + patch: '' } ]; const result = getMatchingFilePaths(comment, changes); - expect(result).toEqual(['app/models/deleted.rb']); + expect(result).toEqual( + changes.filter(c => c.changeType === ChangeType.delete) + ); }); -test('match edited files only', () => { +test('getMatchingFilePaths can match edited files only', () => { const comment: Comment = { pathFilter: ['~app/**'], markdown: 'Only deleted files', @@ -133,20 +146,22 @@ test('match edited files only', () => { const changes: Change[] = [ { file: 'app/models/old.rb', - changeType: ChangeType.edit + changeType: ChangeType.edit, + patch: '' }, { file: 'app/models/deleted.rb', - changeType: ChangeType.delete + changeType: ChangeType.delete, + patch: '' } ]; const result = getMatchingFilePaths(comment, changes); - expect(result).toEqual(['app/models/old.rb']); + expect(result).toEqual(changes.filter(c => c.changeType === ChangeType.edit)); }); -test('disallow multiple modifiers', () => { +test('getMatchingFilePaths disallows multiple modifiers', () => { const comment: Comment = { pathFilter: ['!~app/**'], markdown: 'Not edited files', @@ -156,7 +171,8 @@ test('disallow multiple modifiers', () => { const changes: Change[] = [ { file: 'app/models/old.rb', - changeType: ChangeType.edit + changeType: ChangeType.edit, + patch: '' } ]; @@ -164,3 +180,159 @@ test('disallow multiple modifiers', () => { 'Multiple path modifiers are not supported' ); }); + +test('getMatchingContentChanges returns all changes if contentFilter not defined', () => { + const comment: Comment = { + pathFilter: ['*'], + markdown: 'Everything', + blocking: false + }; + + const changes: Change[] = [ + { + file: 'app/models/old.rb', + changeType: ChangeType.edit, + patch: '' + }, + { + file: 'app/models/new.rb', + changeType: ChangeType.add, + patch: '' + } + ]; + + const result = getMatchingContentChanges(comment, changes); + + expect(result).toEqual(changes); +}); + +test('getMatchingContentChanges returns changes with matching regex', () => { + const comment: Comment = { + pathFilter: ['*'], + markdown: 'Everything', + blocking: false, + contentFilter: [`(\\+\\s*console.log\\(.*\\))`] + }; + + const changes: Change[] = [ + { + file: 'src/services/configReader.ts', + changeType: ChangeType.edit, + patch: + "@@ -4,14 +4,19 @@ import { safeLoad } from 'js-yaml';\n" + + " import { Constants } from '../constants';\n" + + ' \n' + + ' export function getConfiguredComments(filePath: string): Comment[] {\n' + + '+ if (!fs.existsSync(filePath)) {\n' + + '+ throw Error(`Nitpicks file does not exist: ${filePath}`);\n' + + '+ }\n' + + '+\n' + + " const contents = fs.readFileSync(filePath, 'utf8');\n" + + ' const yaml: Comment[] = safeLoad(contents);\n' + + ' \n' + + '+ console.log("this shouldnt have been committed!")\n' + + ' \n' + + ' const comments: Comment[] = yaml\n' + + '- .filter(y => y.markdown && y.pathFilter?.length > 0)\n' + + '+ .filter(y => y.markdown)\n' + + ' .map(c => ({\n' + + ' ...c,\n' + + "- markdown: `${c.markdown}${c.blocking ? Constants.BlockingText : ''}`\n" + + "+ markdown: `${c.markdown}${c.blocking ? Constants.BlockingText : ''}`,\n" + + '+ pathFilter: c.pathFilter ?? Constants.DefaultPathFilter // Default to match everything\n' + + ' }));\n' + + ' \n' + + ' return comments;' + }, + { + file: 'src/models/comment.ts', + changeType: ChangeType.edit, + patch: + '@@ -1,5 +1,6 @@\n' + + ' export interface Comment {\n' + + ' pathFilter: string[];\n' + + '+ contentFilter?: string[];\n' + + ' markdown: string;\n' + + ' blocking: boolean;\n' + + ' }' + } + ]; + + const result = getMatchingContentChanges(comment, changes); + + expect(result).toEqual(changes.slice(0, 1)); +}); + +test('getMatchingContentChanges returns single changes with multiple matching contentFilters', () => { + const comment: Comment = { + pathFilter: ['*'], + markdown: 'Everything', + blocking: false, + contentFilter: [`(\\+\\s*debugger)`, `(\\+\\s*console.log\\(.*\\))`] + }; + + const changes: Change[] = [ + { + file: 'src/services/configReader.ts', + changeType: ChangeType.edit, + patch: + "@@ -4,14 +4,19 @@ import { safeLoad } from 'js-yaml';\n" + + " import { Constants } from '../constants';\n" + + ' \n' + + ' export function getConfiguredComments(filePath: string): Comment[] {\n' + + '+ if (!fs.existsSync(filePath)) {\n' + + '+ throw Error(`Nitpicks file does not exist: ${filePath}`);\n' + + '+ }\n' + + '+\n' + + " const contents = fs.readFileSync(filePath, 'utf8');\n" + + ' const yaml: Comment[] = safeLoad(contents);\n' + + ' \n' + + '+ debugger\n' + + '+ console.log("this shouldnt have been committed!")\n' + + ' \n' + + ' const comments: Comment[] = yaml\n' + + '- .filter(y => y.markdown && y.pathFilter?.length > 0)\n' + + '+ .filter(y => y.markdown)\n' + + ' .map(c => ({\n' + + ' ...c,\n' + + "- markdown: `${c.markdown}${c.blocking ? Constants.BlockingText : ''}`\n" + + "+ markdown: `${c.markdown}${c.blocking ? Constants.BlockingText : ''}`,\n" + + '+ pathFilter: c.pathFilter ?? Constants.DefaultPathFilter // Default to match everything\n' + + ' }));\n' + + ' \n' + + ' return comments;' + } + ]; + + const result = getMatchingContentChanges(comment, changes); + + expect(result).toHaveLength(1); +}); + +test('getMatchingContentChanges throws error if unable to parse regexpr', () => { + const comment: Comment = { + pathFilter: ['*'], + markdown: 'Everything', + blocking: false, + contentFilter: [`(\+\s*console.log\(.*\))`] + }; + + const changes: Change[] = [ + { + file: 'src/models/comment.ts', + changeType: ChangeType.edit, + patch: + '@@ -1,5 +1,6 @@\n' + + ' export interface Comment {\n' + + ' pathFilter: string[];\n' + + '+ contentFilter?: string[];\n' + + ' markdown: string;\n' + + ' blocking: boolean;\n' + + ' }' + } + ]; + + expect(() => getMatchingContentChanges(comment, changes)).toThrowError( + 'Unable to parse regex expression' + ); +}); diff --git a/package.json b/package.json index 7620f76..3016186 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "test": "jest", "lint": "eslint . --ext .ts,.tsx --ignore-pattern node_modules/", "pack": "ncc build", - "all": "npm run build && npm run lint && npm run pack && npm test" + "all": "npm run lint && npm run build && npm test && npm run pack" }, "repository": { "type": "git", diff --git a/src/constants.ts b/src/constants.ts index d1b9e1e..04f2d9a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -4,4 +4,5 @@ export module Constants { '\n__Note__: this is a [blocking](https://github.com/ethanis/nitpicker#blocking-comments) comment.\n'; export const CannedTextSeparator: string = '\n--------------\n_Caused by:_\n'; export const ResolvedText: string = '\n--------------\n_Resolved_\n'; + export const DefaultPathFilter: string[] = ['*']; } diff --git a/src/main.ts b/src/main.ts index 7582cda..5886d44 100644 --- a/src/main.ts +++ b/src/main.ts @@ -37,11 +37,8 @@ async function run() { } const octokit = new github.GitHub(token); - const eventName = process.env.GITHUB_EVENT_NAME; - console.log('starting check'); - const changes: Change[] = await getChangedFiles(octokit, eventName); const targetState = await getTargetState(octokit, comments, changes); diff --git a/src/models/changes.ts b/src/models/changes.ts index 077b04f..3301d6d 100644 --- a/src/models/changes.ts +++ b/src/models/changes.ts @@ -8,4 +8,5 @@ export enum ChangeType { export interface Change { file: string; changeType: ChangeType; + patch: string; } diff --git a/src/models/comment.ts b/src/models/comment.ts index f1142b1..5c2d6f6 100644 --- a/src/models/comment.ts +++ b/src/models/comment.ts @@ -1,5 +1,6 @@ export interface Comment { pathFilter: string[]; + contentFilter?: string[]; markdown: string; blocking: boolean; } diff --git a/src/services/changes.ts b/src/services/changes.ts index bc4c0a1..bb49cf4 100644 --- a/src/services/changes.ts +++ b/src/services/changes.ts @@ -28,16 +28,18 @@ async function getChangesFromSha(octokit: github.GitHub): Promise { return []; } - const listFilesResponse = await octokit.repos.compareCommits({ + const changedFiles = await octokit.repos.compareCommits({ owner: owner, repo: repo, base: beforeSha, - head: afterSha + head: afterSha, + mediaType: { format: 'sha' } }); - const changes = listFilesResponse.data.files.map(f => ({ + const changes: Change[] = changedFiles.data.files.map(f => ({ file: f.filename, - changeType: parseStatus(f.status) + changeType: parseStatus(f.status), + patch: f.patch })); core.debug('found changed files:'); @@ -62,7 +64,8 @@ async function getChangesFromPR(octokit: github.GitHub): Promise { const changes = listFilesResponse.data.map(f => ({ file: f.filename, - changeType: parseStatus(f.status) + changeType: parseStatus(f.status), + patch: f.patch })); core.debug('found changed files:'); diff --git a/src/services/configReader.ts b/src/services/configReader.ts index c600ac2..faf6416 100644 --- a/src/services/configReader.ts +++ b/src/services/configReader.ts @@ -4,14 +4,19 @@ import { safeLoad } from 'js-yaml'; import { Constants } from '../constants'; export function getConfiguredComments(filePath: string): Comment[] { + if (!fs.existsSync(filePath)) { + throw Error(`Nitpicks file does not exist: ${filePath}`); + } + const contents = fs.readFileSync(filePath, 'utf8'); const yaml: Comment[] = safeLoad(contents); const comments: Comment[] = yaml - .filter(y => y.markdown && y.pathFilter?.length > 0) + .filter(y => y.markdown) .map(c => ({ ...c, - markdown: `${c.markdown}${c.blocking ? Constants.BlockingText : ''}` + markdown: `${c.markdown}${c.blocking ? Constants.BlockingText : ''}`, + pathFilter: c.pathFilter ?? Constants.DefaultPathFilter // Default to match everything })); return comments; diff --git a/src/services/state.ts b/src/services/state.ts index 1fddc95..59bd671 100644 --- a/src/services/state.ts +++ b/src/services/state.ts @@ -35,7 +35,10 @@ export async function getTargetState( ); for (const comment of allComments) { - const matches = getMatchingFilePaths(comment, changes); + const matchedFiles = getMatchingFilePaths(comment, changes); + const matchedContent = getMatchingContentChanges(comment, matchedFiles); + const matches = matchedContent.map(m => m.file); + const isApplicable = matches.length > 0; const commentMatchText = `${comment.markdown}${Constants.CannedTextSeparator}`; @@ -80,8 +83,8 @@ export async function getTargetState( export function getMatchingFilePaths( comment: Comment, changes: Change[] -): string[] { - const results: string[] = []; +): Change[] { + const results: Change[] = []; const options: IOptions = { dot: true, nocase: true }; const inclusions: string[] = []; @@ -175,9 +178,42 @@ export function getMatchingFilePaths( // If we've made it this far, comment is good to go if (isMatch) { - results.push(change.file); + results.push(change); } } return results; } + +export function getMatchingContentChanges( + comment: Comment, + changes: Change[] +): Change[] { + // if contentFilter isn't defined, return everything + if (!comment.contentFilter) { + return changes; + } + + const matches: Change[] = []; + + for (const change of changes) { + core.debug(` checking file contents ${change.file}`); + + for (const contentFilter of comment.contentFilter) { + core.debug(` - ${contentFilter}`); + + try { + const regex = new RegExp(contentFilter); + if (regex.test(change.patch)) { + core.debug(` matched contentFilter!`); + matches.push(change); + break; + } + } catch (SyntaxError) { + throw Error(`Unable to parse regex expression: ${contentFilter}`); + } + } + } + + return matches; +}