Replace picomatch with micromatch

Using micromatch, removed `added|modified` rule to simplify the rules. The idea is to extend the output with things like `anyModified` or `anyDeleted` instead of putting that into the matcher rules to keep the matcher rules in sync with `micromatch` rules
This commit is contained in:
Daniil Samoylov 2022-03-01 11:29:18 +13:00
parent 1ec7035ff5
commit 6706fee383
5 changed files with 18279 additions and 795 deletions

View file

@ -87,8 +87,6 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob
# Each filter has a name and a list of rules.
# Rule is a glob expression - paths of all changed
# files are matched against it.
# Rule can optionally specify if the file
# should be added, modified, or deleted.
# For each filter, there will be a corresponding output variable to
# indicate if there's a changed file matching any of the rules.
# Optionally, there can be a second output variable

View file

@ -133,54 +133,6 @@ describe('matching tests', () => {
})
})
describe('matching specific change status', () => {
test('does not match modified file as added', () => {
const yaml = `
add:
- added: "**/*"
`
let filter = new Filter(yaml)
const match = filter.match(modified(['file.js']))
expect(match.add).toEqual([])
})
test('match added file as added', () => {
const yaml = `
add:
- added: "**/*"
`
let filter = new Filter(yaml)
const files = [{status: ChangeStatus.Added, filename: 'file.js'}]
const match = filter.match(files)
expect(match.add).toEqual(files)
})
test('matches when multiple statuses are configured', () => {
const yaml = `
addOrModify:
- added|modified: "**/*"
`
let filter = new Filter(yaml)
const files = [{status: ChangeStatus.Modified, filename: 'file.js'}]
const match = filter.match(files)
expect(match.addOrModify).toEqual(files)
})
test('matches when using an anchor', () => {
const yaml = `
shared: &shared
- common/**/*
- config/**/*
src:
- modified: *shared
`
let filter = new Filter(yaml)
const files = modified(['config/file.js', 'common/anotherFile.js'])
const match = filter.match(files)
expect(match.src).toEqual(files)
})
})
function modified(paths: string[]): File[] {
return paths.map(filename => {
return {filename, status: ChangeStatus.Modified}

18964
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@
"main": "lib/main.js",
"scripts": {
"build": "tsc",
"tscheck": "tsc --noEmit",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint src/**/*.ts",
@ -29,14 +30,13 @@
"@actions/exec": "^1.0.4",
"@actions/github": "^2.2.0",
"@octokit/webhooks": "^7.6.2",
"picomatch": "^2.2.2"
"micromatch": "^4.0.4"
},
"devDependencies": {
"@types/jest": "^25.2.3",
"@types/js-yaml": "^3.12.4",
"@types/minimatch": "^3.0.3",
"@types/micromatch": "^4.0.2",
"@types/node": "^14.0.5",
"@types/picomatch": "^2.2.1",
"@typescript-eslint/parser": "^3.3.0",
"@zeit/ncc": "^0.22.3",
"eslint": "^7.3.0",

View file

@ -1,5 +1,5 @@
import * as jsyaml from 'js-yaml'
import picomatch from 'picomatch'
import micromatch from 'micromatch'
import {File, ChangeStatus} from './file'
// Type definition of object we expect to load from YAML
@ -8,19 +8,18 @@ interface FilterYaml {
}
type FilterItemYaml =
| string // Filename pattern, e.g. "path/to/*.js"
| {[changeTypes: string]: string | string[]} // Change status and filename, e.g. added|modified: "path/to/*.js"
| FilterItemYaml[] // Supports referencing another rule via YAML anchor
// Minimatch options used in all matchers
const MatchOptions = {
// Micromatch options used in all matchers
const MatchOptions: micromatch.Options = {
dot: true
}
// Internal representation of one item in named filter rule
// Created as simplified form of data in FilterItemYaml
interface FilterRuleItem {
status?: ChangeStatus[] // Required change status of the matched files
isMatch: (str: string) => boolean // Matches the filename
/** returns the list of matched files */
matcher: (files: string[]) => string[]
}
export interface FilterResults {
@ -28,7 +27,7 @@ export interface FilterResults {
}
export class Filter {
rules: {[key: string]: FilterRuleItem[]} = {}
rules: {[key: string]: string[]} = {}
// Creates instance of Filter and load rules from YAML if it's provided
constructor(yaml?: string) {
@ -49,49 +48,32 @@ export class Filter {
}
for (const [key, item] of Object.entries(doc)) {
this.rules[key] = this.parseFilterItemYaml(item)
this.rules[key] = this.getPatterns(item)
}
}
match(files: File[]): FilterResults {
const result: FilterResults = {}
const filesMap = files.reduce((result, x) => {
result.set(x.filename, x)
return result
}, new Map<string, File>())
for (const [key, patterns] of Object.entries(this.rules)) {
result[key] = files.filter(file => this.isMatch(file, patterns))
const matchingFileNames = micromatch([...filesMap.keys()], patterns, MatchOptions)
result[key] = matchingFileNames.map(x => filesMap.get(x)).filter((x): x is File => !!x)
}
return result
}
private isMatch(file: File, patterns: FilterRuleItem[]): boolean {
return patterns.some(
rule => (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename)
)
}
private parseFilterItemYaml(item: FilterItemYaml): FilterRuleItem[] {
private getPatterns(item: FilterItemYaml): string[] {
if (Array.isArray(item)) {
return flat(item.map(i => this.parseFilterItemYaml(i)))
return flat(item.map(i => this.getPatterns(i)))
}
if (typeof item === 'string') {
return [{status: undefined, isMatch: picomatch(item, MatchOptions)}]
}
if (typeof item === 'object') {
return Object.entries(item).map(([key, pattern]) => {
if (typeof key !== 'string' || (typeof pattern !== 'string' && !Array.isArray(pattern))) {
this.throwInvalidFormatError(
`Expected [key:string]= pattern:string | string[], but [${key}:${typeof key}]= ${pattern}:${typeof pattern} found`
)
}
return {
status: key
.split('|')
.map(x => x.trim())
.filter(x => x.length > 0)
.map(x => x.toLowerCase()) as ChangeStatus[],
isMatch: picomatch(pattern, MatchOptions)
}
})
return [item]
}
this.throwInvalidFormatError(`Unexpected element type '${typeof item}'`)