This commit is contained in:
Ludy87 2025-08-19 14:45:20 +02:00
parent 4fad01a9ab
commit b2a8b7621d
No known key found for this signature in database
GPG Key ID: 92696155E0220F94
3 changed files with 86 additions and 31 deletions

View File

@ -98,6 +98,31 @@ describe('matching tests', () => {
expect(match.dot).toEqual(files)
})
test('does not match when only negated pattern matches', () => {
const yaml = `
backend:
- src/backend/**
- '!src/frontend/**'
`
const filter = new Filter(yaml)
const files = modified(['vitest.setup.ts'])
const match = filter.match(files)
expect(match.backend).toEqual([])
})
test('negated pattern excludes matching files', () => {
const yaml = `
backend:
- '**/*'
- '!src/frontend/**'
`
const filter = new Filter(yaml)
const backendFile = modified(['src/backend/main.ts'])
const frontendFile = modified(['src/frontend/main.ts'])
expect(filter.match(backendFile).backend).toEqual(backendFile)
expect(filter.match(frontendFile).backend).toEqual([])
})
test('matches all except tsx and less files (negate a group with or-ed parts)', () => {
const yaml = `
backend:

43
dist/index.js vendored
View File

@ -130,33 +130,44 @@ class Filter {
const aPredicate = (rule) => {
return (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename);
};
if (((_a = this.filterConfig) === null || _a === void 0 ? void 0 : _a.predicateQuantifier) === 'every') {
return patterns.every(aPredicate);
}
else {
return patterns.some(aPredicate);
}
const positives = patterns.filter(p => !p.negate);
const negatives = patterns.filter(p => p.negate);
const positiveMatch = positives.length === 0
? true
: ((_a = this.filterConfig) === null || _a === void 0 ? void 0 : _a.predicateQuantifier) === PredicateQuantifier.EVERY
? positives.every(aPredicate)
: positives.some(aPredicate);
const negativeMatch = negatives.some(aPredicate);
return positiveMatch && !negativeMatch;
}
parseFilterItemYaml(item) {
if (Array.isArray(item)) {
return flat(item.map(i => this.parseFilterItemYaml(i)));
}
if (typeof item === 'string') {
return [{ status: undefined, isMatch: (0, picomatch_1.default)(item, MatchOptions) }];
const negated = item.startsWith('!');
const pattern = negated ? item.slice(1) : item;
return [{ status: undefined, isMatch: (0, picomatch_1.default)(pattern, MatchOptions), negate: negated }];
}
if (typeof item === 'object') {
return Object.entries(item).map(([key, pattern]) => {
return Object.entries(item).flatMap(([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()),
isMatch: (0, picomatch_1.default)(pattern, MatchOptions)
};
const patterns = Array.isArray(pattern) ? pattern : [pattern];
return patterns.map(p => {
const negated = p.startsWith('!');
const pat = negated ? p.slice(1) : p;
return {
status: key
.split('|')
.map(x => x.trim())
.filter(x => x.length > 0)
.map(x => x.toLowerCase()),
isMatch: (0, picomatch_1.default)(pat, MatchOptions),
negate: negated
};
});
});
}
this.throwInvalidFormatError(`Unexpected element type '${typeof item}'`);

View File

@ -21,6 +21,7 @@ const MatchOptions = {
interface FilterRuleItem {
status?: ChangeStatus[] // Required change status of the matched files
isMatch: (str: string) => boolean // Matches the filename
negate?: boolean // When true, this rule excludes matching files
}
/**
@ -107,11 +108,20 @@ export class Filter {
const aPredicate = (rule: Readonly<FilterRuleItem>): boolean => {
return (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename)
}
if (this.filterConfig?.predicateQuantifier === 'every') {
return patterns.every(aPredicate)
} else {
return patterns.some(aPredicate)
}
const positives = patterns.filter(p => !p.negate)
const negatives = patterns.filter(p => p.negate)
const positiveMatch =
positives.length === 0
? true
: this.filterConfig?.predicateQuantifier === PredicateQuantifier.EVERY
? positives.every(aPredicate)
: positives.some(aPredicate)
const negativeMatch = negatives.some(aPredicate)
return positiveMatch && !negativeMatch
}
private parseFilterItemYaml(item: FilterItemYaml): FilterRuleItem[] {
@ -120,24 +130,33 @@ export class Filter {
}
if (typeof item === 'string') {
return [{status: undefined, isMatch: picomatch(item, MatchOptions)}]
const negated = item.startsWith('!')
const pattern = negated ? item.slice(1) : item
return [{status: undefined, isMatch: picomatch(pattern, MatchOptions), negate: negated}]
}
if (typeof item === 'object') {
return Object.entries(item).map(([key, pattern]) => {
return Object.entries(item).flatMap(([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)
}
const patterns = Array.isArray(pattern) ? pattern : [pattern]
return patterns.map(p => {
const negated = p.startsWith('!')
const pat = negated ? p.slice(1) : p
return {
status: key
.split('|')
.map(x => x.trim())
.filter(x => x.length > 0)
.map(x => x.toLowerCase()) as ChangeStatus[],
isMatch: picomatch(pat, MatchOptions),
negate: negated
}
})
})
}