Fixes #12110 Trim trailing /** for file exclusions
This commit is contained in:
parent
47393fb852
commit
9e228b20ff
3 changed files with 88 additions and 31 deletions
|
@ -212,10 +212,15 @@ function parseRegExp(pattern: string): string {
|
||||||
const T1 = /^\*\*\/\*\.[\w\.-]+$/; // **/*.something
|
const T1 = /^\*\*\/\*\.[\w\.-]+$/; // **/*.something
|
||||||
const T2 = /^\*\*\/[\w\.-]+$/; // **/something
|
const T2 = /^\*\*\/[\w\.-]+$/; // **/something
|
||||||
const T3 = /^{\*\*\/[\*\.]?[\w\.-]+(,\*\*\/[\*\.]?[\w\.-]+)*}$/; // {**/*.something,**/*.else} or {**/package.json,**/project.json}
|
const T3 = /^{\*\*\/[\*\.]?[\w\.-]+(,\*\*\/[\*\.]?[\w\.-]+)*}$/; // {**/*.something,**/*.else} or {**/package.json,**/project.json}
|
||||||
|
const T3_2 = /^{\*\*\/[\*\.]?[\w\.-]+(\/\*\*)?(,\*\*\/[\*\.]?[\w\.-]+(\/\*\*)?)*}$/; // Like T3, with optional trailing /**
|
||||||
|
|
||||||
export type ParsedPattern = (path: string, basename?: string) => boolean;
|
export type ParsedPattern = (path: string, basename?: string) => boolean;
|
||||||
export type ParsedExpression = (path: string, basename?: string, siblingsFn?: () => string[]) => string /* the matching pattern */;
|
export type ParsedExpression = (path: string, basename?: string, siblingsFn?: () => string[]) => string /* the matching pattern */;
|
||||||
|
|
||||||
|
export interface IGlobOptions {
|
||||||
|
trimForExclusions?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface ParsedStringPattern {
|
interface ParsedStringPattern {
|
||||||
(path: string, basename: string): string /* the matching pattern */;
|
(path: string, basename: string): string /* the matching pattern */;
|
||||||
basenames?: string[];
|
basenames?: string[];
|
||||||
|
@ -239,7 +244,7 @@ const NULL = function(): string {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function parsePattern(pattern: string): ParsedStringPattern {
|
function parsePattern(pattern: string, options: IGlobOptions): ParsedStringPattern {
|
||||||
if (!pattern) {
|
if (!pattern) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -248,37 +253,26 @@ function parsePattern(pattern: string): ParsedStringPattern {
|
||||||
pattern = pattern.trim();
|
pattern = pattern.trim();
|
||||||
|
|
||||||
// Check cache
|
// Check cache
|
||||||
let parsedPattern = CACHE.get(pattern);
|
const patternKey = `${pattern}_${!!options.trimForExclusions}`;
|
||||||
|
let parsedPattern = CACHE.get(patternKey);
|
||||||
if (parsedPattern) {
|
if (parsedPattern) {
|
||||||
return parsedPattern;
|
return parsedPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Trivias
|
// Check for Trivias
|
||||||
|
let trimmedPattern;
|
||||||
if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check
|
if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check
|
||||||
const base = pattern.substr(4); // '**/*'.length === 4
|
const base = pattern.substr(4); // '**/*'.length === 4
|
||||||
parsedPattern = function (path, basename) {
|
parsedPattern = function (path, basename) {
|
||||||
return path && strings.endsWith(path, base) ? pattern : null;
|
return path && strings.endsWith(path, base) ? pattern : null;
|
||||||
};
|
};
|
||||||
} else if (T2.test(pattern)) { // common pattern: **/some.txt just need basename check
|
} else if (T2.test(pattern)) { // common pattern: **/some.txt just need basename check
|
||||||
const base = pattern.substr(3); // '**/'.length === 3
|
parsedPattern = trivia2(pattern, pattern);
|
||||||
const slashBase = `/${base}`;
|
} else if (options.trimForExclusions && strings.endsWith(pattern, '/**') && T2.test(trimmedPattern = pattern.substr(0, pattern.length - 3))) { // common pattern: **/some/** for exclusions just need basename check
|
||||||
const backslashBase = `\\${base}`;
|
parsedPattern = trivia2(trimmedPattern, pattern);
|
||||||
parsedPattern = function (path, basename) {
|
} else if ((options.trimForExclusions ? T3_2 : T3).test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png}
|
||||||
if (!path) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (basename) {
|
|
||||||
return basename === base ? pattern : null;
|
|
||||||
}
|
|
||||||
return path === base || strings.endsWith(path, slashBase) || strings.endsWith(path, backslashBase) ? pattern : null;
|
|
||||||
};
|
|
||||||
const basenames = [base];
|
|
||||||
parsedPattern.basenames = basenames;
|
|
||||||
parsedPattern.patterns = [pattern];
|
|
||||||
parsedPattern.allBasenames = basenames;
|
|
||||||
} else if (T3.test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png}
|
|
||||||
const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1).split(',')
|
const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1).split(',')
|
||||||
.map(pattern => parsePattern(pattern))
|
.map(pattern => parsePattern(pattern, options))
|
||||||
.filter(pattern => pattern !== NULL), pattern);
|
.filter(pattern => pattern !== NULL), pattern);
|
||||||
const n = parsedPatterns.length;
|
const n = parsedPatterns.length;
|
||||||
if (!n) {
|
if (!n) {
|
||||||
|
@ -307,11 +301,32 @@ function parsePattern(pattern: string): ParsedStringPattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache
|
// Cache
|
||||||
CACHE.set(pattern, parsedPattern);
|
CACHE.set(patternKey, parsedPattern);
|
||||||
|
|
||||||
return parsedPattern;
|
return parsedPattern;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// common pattern: **/some.txt just need basename check
|
||||||
|
function trivia2(pattern, originalPattern): ParsedStringPattern {
|
||||||
|
const base = pattern.substr(3); // '**/'.length === 3
|
||||||
|
const slashBase = `/${base}`;
|
||||||
|
const backslashBase = `\\${base}`;
|
||||||
|
const parsedPattern: ParsedStringPattern = function (path, basename) {
|
||||||
|
if (!path) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (basename) {
|
||||||
|
return basename === base ? originalPattern : null;
|
||||||
|
}
|
||||||
|
return path === base || strings.endsWith(path, slashBase) || strings.endsWith(path, backslashBase) ? originalPattern : null;
|
||||||
|
};
|
||||||
|
const basenames = [base];
|
||||||
|
parsedPattern.basenames = basenames;
|
||||||
|
parsedPattern.patterns = [originalPattern];
|
||||||
|
parsedPattern.allBasenames = basenames;
|
||||||
|
return parsedPattern;
|
||||||
|
}
|
||||||
|
|
||||||
function toRegExp(pattern: string): ParsedStringPattern {
|
function toRegExp(pattern: string): ParsedStringPattern {
|
||||||
try {
|
try {
|
||||||
const regExp = new RegExp(`^${parseRegExp(pattern)}$`);
|
const regExp = new RegExp(`^${parseRegExp(pattern)}$`);
|
||||||
|
@ -350,16 +365,16 @@ export function match(arg1: string | IExpression, path: string, siblingsFn?: ()
|
||||||
* - simple brace expansion ({js,ts} => js or ts)
|
* - simple brace expansion ({js,ts} => js or ts)
|
||||||
* - character ranges (using [...])
|
* - character ranges (using [...])
|
||||||
*/
|
*/
|
||||||
export function parse(pattern: string): ParsedPattern;
|
export function parse(pattern: string, options?: IGlobOptions): ParsedPattern;
|
||||||
export function parse(expression: IExpression): ParsedExpression;
|
export function parse(expression: IExpression, options?: IGlobOptions): ParsedExpression;
|
||||||
export function parse(arg1: string | IExpression): any {
|
export function parse(arg1: string | IExpression, options: IGlobOptions = {}): any {
|
||||||
if (!arg1) {
|
if (!arg1) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glob with String
|
// Glob with String
|
||||||
if (typeof arg1 === 'string') {
|
if (typeof arg1 === 'string') {
|
||||||
const parsedPattern = parsePattern(arg1);
|
const parsedPattern = parsePattern(arg1, options);
|
||||||
if (parsedPattern === NULL) {
|
if (parsedPattern === NULL) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
@ -373,16 +388,16 @@ export function parse(arg1: string | IExpression): any {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glob with Expression
|
// Glob with Expression
|
||||||
return parsedExpression(<IExpression>arg1);
|
return parsedExpression(<IExpression>arg1, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
|
export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
|
||||||
return (<ParsedStringPattern>patternOrExpression).allBasenames || [];
|
return (<ParsedStringPattern>patternOrExpression).allBasenames || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsedExpression(expression: IExpression): ParsedExpression {
|
function parsedExpression(expression: IExpression, options: IGlobOptions): ParsedExpression {
|
||||||
const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
|
const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
|
||||||
.map(pattern => parseExpressionPattern(pattern, expression[pattern]))
|
.map(pattern => parseExpressionPattern(pattern, expression[pattern], options))
|
||||||
.filter(pattern => pattern !== NULL));
|
.filter(pattern => pattern !== NULL));
|
||||||
|
|
||||||
const n = parsedPatterns.length;
|
const n = parsedPatterns.length;
|
||||||
|
@ -454,12 +469,12 @@ function parsedExpression(expression: IExpression): ParsedExpression {
|
||||||
return resultExpression;
|
return resultExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseExpressionPattern(pattern: string, value: any): (ParsedStringPattern | ParsedExpressionPattern) {
|
function parseExpressionPattern(pattern: string, value: any, options: IGlobOptions): (ParsedStringPattern | ParsedExpressionPattern) {
|
||||||
if (value === false) {
|
if (value === false) {
|
||||||
return NULL; // pattern is disabled
|
return NULL; // pattern is disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedPattern = parsePattern(pattern);
|
const parsedPattern = parsePattern(pattern, options);
|
||||||
if (parsedPattern === NULL) {
|
if (parsedPattern === NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -712,4 +712,46 @@ suite('Glob', () => {
|
||||||
'**/bar': true
|
'**/bar': true
|
||||||
})), ['bar']);
|
})), ['bar']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('expression/pattern optimization for basenames', function () {
|
||||||
|
assert.deepStrictEqual(glob.getBasenameTerms(glob.parse('**/foo/**')), []);
|
||||||
|
assert.deepStrictEqual(glob.getBasenameTerms(glob.parse('**/foo/**', { trimForExclusions: true })), ['foo']);
|
||||||
|
|
||||||
|
testOptimizationForBasenames('**/*.foo/**', [], [['baz/bar.foo/bar/baz', true]]);
|
||||||
|
testOptimizationForBasenames('**/foo/**', ['foo'], [['bar/foo', true], ['bar/foo/baz', false]]);
|
||||||
|
testOptimizationForBasenames('{**/baz/**,**/foo/**}', ['baz', 'foo'], [['bar/baz', true], ['bar/foo', true]]);
|
||||||
|
|
||||||
|
testOptimizationForBasenames({
|
||||||
|
'**/foo/**': true,
|
||||||
|
'{**/bar/**,**/baz/**}': true,
|
||||||
|
'**/bulb/**': false
|
||||||
|
}, ['foo', 'bar', 'baz'], [
|
||||||
|
['bar/foo', '**/foo/**'],
|
||||||
|
['foo/bar', '{**/bar/**,**/baz/**}'],
|
||||||
|
['bar/nope', null]
|
||||||
|
]);
|
||||||
|
|
||||||
|
const siblingsFn = () => ['baz', 'baz.zip', 'nope'];
|
||||||
|
testOptimizationForBasenames({
|
||||||
|
'**/foo/**': { when: '$(basename).zip' },
|
||||||
|
'**/bar/**': true
|
||||||
|
}, ['bar'], [
|
||||||
|
['bar/foo', null],
|
||||||
|
['bar/foo/baz', null],
|
||||||
|
['bar/foo/nope', null],
|
||||||
|
['foo/bar', '**/bar/**'],
|
||||||
|
], [
|
||||||
|
null,
|
||||||
|
siblingsFn,
|
||||||
|
siblingsFn
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
function testOptimizationForBasenames(pattern: string|glob.IExpression, basenameTerms: string[], matches: [string, string|boolean][], siblingsFns: (() => string[])[] = []) {
|
||||||
|
const parsed = glob.parse(<glob.IExpression>pattern, { trimForExclusions: true });
|
||||||
|
assert.deepStrictEqual(glob.getBasenameTerms(parsed), basenameTerms);
|
||||||
|
matches.forEach(([text, result], i) => {
|
||||||
|
assert.strictEqual(parsed(text, null, siblingsFns[i]), result);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
|
@ -67,7 +67,7 @@ export class FileWalker {
|
||||||
constructor(config: IRawSearch) {
|
constructor(config: IRawSearch) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.filePattern = config.filePattern;
|
this.filePattern = config.filePattern;
|
||||||
this.excludePattern = glob.parse(config.excludePattern);
|
this.excludePattern = glob.parse(config.excludePattern, { trimForExclusions: true });
|
||||||
this.includePattern = config.includePattern && glob.parse(config.includePattern);
|
this.includePattern = config.includePattern && glob.parse(config.includePattern);
|
||||||
this.maxResults = config.maxResults || null;
|
this.maxResults = config.maxResults || null;
|
||||||
this.maxFilesize = config.maxFilesize || null;
|
this.maxFilesize = config.maxFilesize || null;
|
||||||
|
|
Loading…
Reference in a new issue