mirror of
https://github.com/Microsoft/vscode
synced 2024-10-12 22:37:41 +00:00
Fixes #12110 Trim trailing /** for file exclusions
This commit is contained in:
parent
47393fb852
commit
9e228b20ff
|
@ -212,10 +212,15 @@ function parseRegExp(pattern: string): string {
|
|||
const T1 = /^\*\*\/\*\.[\w\.-]+$/; // **/*.something
|
||||
const T2 = /^\*\*\/[\w\.-]+$/; // **/something
|
||||
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 ParsedExpression = (path: string, basename?: string, siblingsFn?: () => string[]) => string /* the matching pattern */;
|
||||
|
||||
export interface IGlobOptions {
|
||||
trimForExclusions?: boolean;
|
||||
}
|
||||
|
||||
interface ParsedStringPattern {
|
||||
(path: string, basename: string): string /* the matching pattern */;
|
||||
basenames?: string[];
|
||||
|
@ -239,7 +244,7 @@ const NULL = function(): string {
|
|||
return null;
|
||||
};
|
||||
|
||||
function parsePattern(pattern: string): ParsedStringPattern {
|
||||
function parsePattern(pattern: string, options: IGlobOptions): ParsedStringPattern {
|
||||
if (!pattern) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -248,37 +253,26 @@ function parsePattern(pattern: string): ParsedStringPattern {
|
|||
pattern = pattern.trim();
|
||||
|
||||
// Check cache
|
||||
let parsedPattern = CACHE.get(pattern);
|
||||
const patternKey = `${pattern}_${!!options.trimForExclusions}`;
|
||||
let parsedPattern = CACHE.get(patternKey);
|
||||
if (parsedPattern) {
|
||||
return parsedPattern;
|
||||
}
|
||||
|
||||
// Check for Trivias
|
||||
let trimmedPattern;
|
||||
if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check
|
||||
const base = pattern.substr(4); // '**/*'.length === 4
|
||||
parsedPattern = function (path, basename) {
|
||||
return path && strings.endsWith(path, base) ? pattern : null;
|
||||
};
|
||||
} else if (T2.test(pattern)) { // common pattern: **/some.txt just need basename check
|
||||
const base = pattern.substr(3); // '**/'.length === 3
|
||||
const slashBase = `/${base}`;
|
||||
const backslashBase = `\\${base}`;
|
||||
parsedPattern = function (path, basename) {
|
||||
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}
|
||||
parsedPattern = trivia2(pattern, pattern);
|
||||
} 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
|
||||
parsedPattern = trivia2(trimmedPattern, pattern);
|
||||
} else if ((options.trimForExclusions ? T3_2 : T3).test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png}
|
||||
const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1).split(',')
|
||||
.map(pattern => parsePattern(pattern))
|
||||
.map(pattern => parsePattern(pattern, options))
|
||||
.filter(pattern => pattern !== NULL), pattern);
|
||||
const n = parsedPatterns.length;
|
||||
if (!n) {
|
||||
|
@ -307,11 +301,32 @@ function parsePattern(pattern: string): ParsedStringPattern {
|
|||
}
|
||||
|
||||
// Cache
|
||||
CACHE.set(pattern, parsedPattern);
|
||||
CACHE.set(patternKey, 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 {
|
||||
try {
|
||||
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)
|
||||
* - character ranges (using [...])
|
||||
*/
|
||||
export function parse(pattern: string): ParsedPattern;
|
||||
export function parse(expression: IExpression): ParsedExpression;
|
||||
export function parse(arg1: string | IExpression): any {
|
||||
export function parse(pattern: string, options?: IGlobOptions): ParsedPattern;
|
||||
export function parse(expression: IExpression, options?: IGlobOptions): ParsedExpression;
|
||||
export function parse(arg1: string | IExpression, options: IGlobOptions = {}): any {
|
||||
if (!arg1) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Glob with String
|
||||
if (typeof arg1 === 'string') {
|
||||
const parsedPattern = parsePattern(arg1);
|
||||
const parsedPattern = parsePattern(arg1, options);
|
||||
if (parsedPattern === NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -373,16 +388,16 @@ export function parse(arg1: string | IExpression): any {
|
|||
}
|
||||
|
||||
// Glob with Expression
|
||||
return parsedExpression(<IExpression>arg1);
|
||||
return parsedExpression(<IExpression>arg1, options);
|
||||
}
|
||||
|
||||
export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
|
||||
return (<ParsedStringPattern>patternOrExpression).allBasenames || [];
|
||||
}
|
||||
|
||||
function parsedExpression(expression: IExpression): ParsedExpression {
|
||||
function parsedExpression(expression: IExpression, options: IGlobOptions): ParsedExpression {
|
||||
const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
|
||||
.map(pattern => parseExpressionPattern(pattern, expression[pattern]))
|
||||
.map(pattern => parseExpressionPattern(pattern, expression[pattern], options))
|
||||
.filter(pattern => pattern !== NULL));
|
||||
|
||||
const n = parsedPatterns.length;
|
||||
|
@ -454,12 +469,12 @@ function parsedExpression(expression: IExpression): ParsedExpression {
|
|||
return resultExpression;
|
||||
}
|
||||
|
||||
function parseExpressionPattern(pattern: string, value: any): (ParsedStringPattern | ParsedExpressionPattern) {
|
||||
function parseExpressionPattern(pattern: string, value: any, options: IGlobOptions): (ParsedStringPattern | ParsedExpressionPattern) {
|
||||
if (value === false) {
|
||||
return NULL; // pattern is disabled
|
||||
}
|
||||
|
||||
const parsedPattern = parsePattern(pattern);
|
||||
const parsedPattern = parsePattern(pattern, options);
|
||||
if (parsedPattern === NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -712,4 +712,46 @@ suite('Glob', () => {
|
|||
'**/bar': true
|
||||
})), ['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) {
|
||||
this.config = config;
|
||||
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.maxResults = config.maxResults || null;
|
||||
this.maxFilesize = config.maxFilesize || null;
|
||||
|
|
Loading…
Reference in a new issue