-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
glob.ts
742 lines (630 loc) · 27.7 KB
/
glob.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
// *****************************************************************************
// Copyright (C) 2018 Red Hat, Inc. and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
// *****************************************************************************
// copied from https://github.com/Microsoft/vscode/blob/bf7ac9201e7a7d01741d4e6e64b5dc9f3197d97b/src/vs/base/common/glob.ts
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as strings from './strings';
import * as paths from './paths';
import { CharCode } from './char-code';
/* eslint-disable @typescript-eslint/no-shadow, no-null/no-null */
export interface IExpression {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[pattern: string]: boolean | SiblingClause | any;
}
export interface IRelativePattern {
base: string;
pattern: string;
pathToRelative(from: string, to: string): string;
}
export function getEmptyExpression(): IExpression {
return Object.create(null);
}
export interface SiblingClause {
when: string;
}
const GLOBSTAR = '**';
const GLOB_SPLIT = '/';
const PATH_REGEX = '[/\\\\]'; // any slash or backslash
const NO_PATH_REGEX = '[^/\\\\]'; // any non-slash and non-backslash
const ALL_FORWARD_SLASHES = /\//g;
function starsToRegExp(starCount: number): string {
switch (starCount) {
case 0:
return '';
case 1:
return `${NO_PATH_REGEX}*?`; // 1 star matches any number of characters except path separator (/ and \) - non greedy (?)
default:
// Matches: (Path Sep OR Path Val followed by Path Sep OR Path Sep followed by Path Val) 0-many times
// Group is non capturing because we don't need to capture at all (?:...)
// Overall we use non-greedy matching because it could be that we match too much
return `(?:${PATH_REGEX}|${NO_PATH_REGEX}+${PATH_REGEX}|${PATH_REGEX}${NO_PATH_REGEX}+)*?`;
}
}
export function splitGlobAware(pattern: string, splitChar: string): string[] {
if (!pattern) {
return [];
}
const segments: string[] = [];
let inBraces = false;
let inBrackets = false;
let char: string;
let curVal = '';
for (let i = 0; i < pattern.length; i++) {
char = pattern[i];
switch (char) {
case splitChar:
if (!inBraces && !inBrackets) {
segments.push(curVal);
curVal = '';
continue;
}
break;
case '{':
inBraces = true;
break;
case '}':
inBraces = false;
break;
case '[':
inBrackets = true;
break;
case ']':
inBrackets = false;
break;
}
curVal += char;
}
// Tail
if (curVal) {
segments.push(curVal);
}
return segments;
}
function parseRegExp(pattern: string): string {
if (!pattern) {
return '';
}
let regEx = '';
// Split up into segments for each slash found
// eslint-disable-next-line prefer-const
let segments = splitGlobAware(pattern, GLOB_SPLIT);
// Special case where we only have globstars
if (segments.every(s => s === GLOBSTAR)) {
regEx = '.*';
}
// Build regex over segments
// tslint:disable-next-line:one-line
else {
let previousSegmentWasGlobStar = false;
segments.forEach((segment, index) => {
// Globstar is special
if (segment === GLOBSTAR) {
// if we have more than one globstar after another, just ignore it
if (!previousSegmentWasGlobStar) {
regEx += starsToRegExp(2);
previousSegmentWasGlobStar = true;
}
return;
}
// States
let inBraces = false;
let braceVal = '';
let inBrackets = false;
let bracketVal = '';
let char: string;
for (let i = 0; i < segment.length; i++) {
char = segment[i];
// Support brace expansion
if (char !== '}' && inBraces) {
braceVal += char;
continue;
}
// Support brackets
if (inBrackets && (char !== ']' || !bracketVal) /* ] is literally only allowed as first character in brackets to match it */) {
let res: string;
// range operator
if (char === '-') {
res = char;
}
// negation operator (only valid on first index in bracket)
// tslint:disable-next-line:one-line
else if ((char === '^' || char === '!') && !bracketVal) {
res = '^';
}
// glob split matching is not allowed within character ranges
// see http://man7.org/linux/man-pages/man7/glob.7.html
// tslint:disable-next-line:one-line
else if (char === GLOB_SPLIT) {
res = '';
}
// anything else gets escaped
// tslint:disable-next-line:one-line
else {
res = strings.escapeRegExpCharacters(char);
}
bracketVal += res;
continue;
}
switch (char) {
case '{':
inBraces = true;
continue;
case '[':
inBrackets = true;
continue;
case '}':
// eslint-disable-next-line prefer-const
let choices = splitGlobAware(braceVal, ',');
// Converts {foo,bar} => [foo|bar]
// eslint-disable-next-line prefer-const
let braceRegExp = `(?:${choices.map(c => parseRegExp(c)).join('|')})`;
regEx += braceRegExp;
inBraces = false;
braceVal = '';
break;
case ']':
regEx += ('[' + bracketVal + ']');
inBrackets = false;
bracketVal = '';
break;
case '?':
regEx += NO_PATH_REGEX; // 1 ? matches any single character except path separator (/ and \)
continue;
case '*':
regEx += starsToRegExp(1);
continue;
default:
regEx += strings.escapeRegExpCharacters(char);
}
}
// Tail: Add the slash we had split on if there is more to come and the remaining pattern is not a globstar
// For example if pattern: some/**/*.js we want the "/" after some to be included in the RegEx to prevent
// a folder called "something" to match as well.
// However, if pattern: some/**, we tolerate that we also match on "something" because our globstar behavior
// is to match 0-N segments.
if (index < segments.length - 1 && (segments[index + 1] !== GLOBSTAR || index + 2 < segments.length)) {
regEx += PATH_REGEX;
}
// reset state
previousSegmentWasGlobStar = false;
});
}
return regEx;
}
// regexes to check for trivial glob patterns that just check for String#endsWith
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 /**
const T4 = /^\*\*((\/[\w\.-]+)+)\/?$/; // **/something/else
const T5 = /^([\w\.-]+(\/[\w\.-]+)*)\/?$/; // something/else
export type ParsedPattern = (path: string, basename?: string) => boolean;
// The ParsedExpression returns a Promise iff hasSibling returns a Promise.
// eslint-disable-next-line max-len
export type ParsedExpression = (path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise<boolean>) => string | Promise<string> /* the matching pattern */;
export interface IGlobOptions {
/**
* Simplify patterns for use as exclusion filters during tree traversal to skip entire subtrees. Cannot be used outside of a tree traversal.
*/
trimForExclusions?: boolean;
}
interface ParsedStringPattern {
(path: string, basename: string): string | Promise<string> /* the matching pattern */;
basenames?: string[];
patterns?: string[];
allBasenames?: string[];
allPaths?: string[];
}
interface ParsedExpressionPattern {
(path: string, basename: string, name: string, hasSibling: (name: string) => boolean | Promise<boolean>): string | Promise<string> /* the matching pattern */;
requiresSiblings?: boolean;
allBasenames?: string[];
allPaths?: string[];
}
const CACHE = new Map<string, ParsedStringPattern>(); // new LRUCache<string, ParsedStringPattern>(10000); // bounded to 10000 elements
const FALSE = function (): boolean {
return false;
};
const NULL = function (): string {
return null!;
};
function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): ParsedStringPattern {
if (!arg1) {
return NULL;
}
// Handle IRelativePattern
let pattern: string;
if (typeof arg1 !== 'string') {
pattern = arg1.pattern;
} else {
pattern = arg1;
}
// Whitespace trimming
pattern = pattern.trim();
// Check cache
const patternKey = `${pattern}_${!!options.trimForExclusions}`;
let parsedPattern = CACHE.get(patternKey);
if (parsedPattern) {
return wrapRelativePattern(parsedPattern, arg1);
}
// Check for Trivias
let match: RegExpExecArray;
if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check
const base = pattern.substr(4); // '**/*'.length === 4
parsedPattern = function (path, basename): string {
return path && strings.endsWith(path, base) ? pattern : null!;
};
} else if (match = T2.exec(trimForExclusions(pattern, options))!) { // common pattern: **/some.txt just need basename check
parsedPattern = trivia2(match[1], pattern);
} else if ((options.trimForExclusions ? T3_2 : T3).test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png}
parsedPattern = trivia3(pattern, options);
} else if (match = T4.exec(trimForExclusions(pattern, options))!) { // common pattern: **/something/else just need endsWith check
parsedPattern = trivia4and5(match[1].substr(1), pattern, true);
} else if (match = T5.exec(trimForExclusions(pattern, options))!) { // common pattern: something/else just need equals check
parsedPattern = trivia4and5(match[1], pattern, false);
}
// Otherwise convert to pattern
// tslint:disable-next-line:one-line
else {
parsedPattern = toRegExp(pattern);
}
// Cache
CACHE.set(patternKey, parsedPattern);
return wrapRelativePattern(parsedPattern, arg1);
}
function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | IRelativePattern): ParsedStringPattern {
if (typeof arg2 === 'string') {
return parsedPattern;
}
return function (path, basename): string | Promise<string> {
if (!paths.isEqualOrParent(path, arg2.base)) {
return null!;
}
return parsedPattern(paths.normalize(arg2.pathToRelative(arg2.base, path)), basename);
};
}
function trimForExclusions(pattern: string, options: IGlobOptions): string {
return options.trimForExclusions && strings.endsWith(pattern, '/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
}
// common pattern: **/some.txt just need basename check
function trivia2(base: string, originalPattern: string): ParsedStringPattern {
const slashBase = `/${base}`;
const backslashBase = `\\${base}`;
const parsedPattern: ParsedStringPattern = function (path, basename): string {
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;
}
// repetition of common patterns (see above) {**/*.txt,**/*.png}
function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern {
const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1).split(',')
.map(pattern => parsePattern(pattern, options))
.filter(pattern => pattern !== NULL), pattern);
const n = parsedPatterns.length;
if (!n) {
return NULL;
}
if (n === 1) {
return <ParsedStringPattern>parsedPatterns[0];
}
const parsedPattern: ParsedStringPattern = function (path: string, basename: string): string {
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
if ((<ParsedStringPattern>parsedPatterns[i])(path, basename)) {
return pattern;
}
}
return null!;
};
const withBasenames = parsedPatterns.find(pattern => !!(<ParsedStringPattern>pattern).allBasenames);
// const withBasenames = arrays.first(parsedPatterns, pattern => !!(<ParsedStringPattern>pattern).allBasenames);
if (withBasenames) {
parsedPattern.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
}
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, <string[]>[]);
if (allPaths.length) {
parsedPattern.allPaths = allPaths;
}
return parsedPattern;
}
// common patterns: **/something/else just need endsWith check, something/else just needs and equals check
function trivia4and5(path: string, pattern: string, matchPathEnds: boolean): ParsedStringPattern {
const nativePath = paths.nativeSep !== paths.sep ? path.replace(ALL_FORWARD_SLASHES, paths.nativeSep) : path;
const nativePathEnd = paths.nativeSep + nativePath;
// eslint-disable-next-line @typescript-eslint/no-shadow
const parsedPattern: ParsedStringPattern = matchPathEnds ? function (path, basename): string {
return path && (path === nativePath || strings.endsWith(path, nativePathEnd)) ? pattern : null!;
// eslint-disable-next-line @typescript-eslint/no-shadow
} : function (path, basename): string {
return path && path === nativePath ? pattern : null!;
};
parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + path];
return parsedPattern;
}
function toRegExp(pattern: string): ParsedStringPattern {
try {
const regExp = new RegExp(`^${parseRegExp(pattern)}$`);
return function (path: string, basename: string): string {
regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it!
return path && regExp.test(path) ? pattern : null!;
};
} catch (error) {
return NULL;
}
}
/**
* Simplified glob matching. Supports a subset of glob patterns:
* - * matches anything inside a path segment
* - ? matches 1 character inside a path segment
* - ** matches anything including an empty path segment
* - simple brace expansion ({js,ts} => js or ts)
* - character ranges (using [...])
*/
export function match(pattern: string | IRelativePattern, path: string): boolean;
export function match(expression: IExpression, path: string, hasSibling?: (name: string) => boolean): string /* the matching pattern */;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function match(arg1: string | IExpression | IRelativePattern, path: string, hasSibling?: (name: string) => boolean): any {
if (!arg1 || !path) {
return false;
}
return parse(<IExpression>arg1)(path, undefined, hasSibling);
}
/**
* Simplified glob matching. Supports a subset of glob patterns:
* - * matches anything inside a path segment
* - ? matches 1 character inside a path segment
* - ** matches anything including an empty path segment
* - simple brace expansion ({js,ts} => js or ts)
* - character ranges (using [...])
*/
export function parse(pattern: string | IRelativePattern, options?: IGlobOptions): ParsedPattern;
export function parse(expression: IExpression, options?: IGlobOptions): ParsedExpression;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function parse(arg1: string | IExpression | IRelativePattern, options: IGlobOptions = {}): any {
if (!arg1) {
return FALSE;
}
// Glob with String
if (typeof arg1 === 'string' || isRelativePattern(arg1)) {
const parsedPattern = parsePattern(arg1 as string | IRelativePattern, options);
if (parsedPattern === NULL) {
return FALSE;
}
const resultPattern = function (path: string, basename: string): boolean {
return !!parsedPattern(path, basename);
};
if (parsedPattern.allBasenames) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<ParsedStringPattern><any>resultPattern).allBasenames = parsedPattern.allBasenames;
}
if (parsedPattern.allPaths) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(<ParsedStringPattern><any>resultPattern).allPaths = parsedPattern.allPaths;
}
return resultPattern;
}
// Glob with Expression
return parsedExpression(<IExpression>arg1, options);
}
export function hasSiblingPromiseFn(siblingsFn?: () => Promise<string[]>): ((name: string) => Promise<boolean>) | undefined {
if (!siblingsFn) {
return undefined;
}
let siblings: Promise<Record<string, true>>;
return (name: string) => {
if (!siblings) {
siblings = (siblingsFn() || Promise.resolve([]))
.then(list => list ? listToMap(list) : {});
}
return siblings.then(map => !!map[name]);
};
}
export function hasSiblingFn(siblingsFn?: () => string[]): ((name: string) => boolean) | undefined {
if (!siblingsFn) {
return undefined;
}
let siblings: Record<string, true>;
return (name: string) => {
if (!siblings) {
const list = siblingsFn();
siblings = list ? listToMap(list) : {};
}
return !!siblings[name];
};
}
function listToMap(list: string[]): Record<string, true> {
const map: Record<string, true> = {};
for (const key of list) {
map[key] = true;
}
return map;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isRelativePattern(obj: any): obj is IRelativePattern {
const rp = obj as IRelativePattern;
return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string' && typeof rp.pathToRelative === 'function';
}
/**
* Same as `parse`, but the ParsedExpression is guaranteed to return a Promise
*/
export function parseToAsync(expression: IExpression, options?: IGlobOptions): ParsedExpression {
// eslint-disable-next-line @typescript-eslint/no-shadow
const parsedExpression = parse(expression, options);
return (path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise<boolean>): string | Promise<string> => {
const result = parsedExpression(path, basename, hasSibling);
return result instanceof Promise ? result : Promise.resolve(result);
};
}
export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
return (<ParsedStringPattern>patternOrExpression).allBasenames || [];
}
export function getPathTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
return (<ParsedStringPattern>patternOrExpression).allPaths || [];
}
function parsedExpression(expression: IExpression, options: IGlobOptions): ParsedExpression {
const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
.map(pattern => parseExpressionPattern(pattern, expression[pattern], options))
.filter(pattern => pattern !== NULL));
const n = parsedPatterns.length;
if (!n) {
return NULL;
}
if (!parsedPatterns.some(parsedPattern => (<ParsedExpressionPattern>parsedPattern).requiresSiblings!)) {
if (n === 1) {
return <ParsedStringPattern>parsedPatterns[0];
}
// eslint-disable-next-line @typescript-eslint/no-shadow
const resultExpression: ParsedStringPattern = function (path: string, basename: string): string | Promise<string> {
// eslint-disable-next-line @typescript-eslint/no-shadow
// tslint:disable-next-line:one-variable-per-declaration
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
// Pattern matches path
const result = (<ParsedStringPattern>parsedPatterns[i])(path, basename);
if (result) {
return result;
}
}
return null!;
};
// eslint-disable-next-line @typescript-eslint/no-shadow
const withBasenames = parsedPatterns.find(pattern => !!(<ParsedStringPattern>pattern).allBasenames);
if (withBasenames) {
resultExpression.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
}
// eslint-disable-next-line @typescript-eslint/no-shadow
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, <string[]>[]);
if (allPaths.length) {
resultExpression.allPaths = allPaths;
}
return resultExpression;
}
const resultExpression: ParsedStringPattern = function (path: string, basename: string, hasSibling?: (name: string) => boolean | Promise<boolean>): string | Promise<string> {
let name: string = null!;
// eslint-disable-next-line @typescript-eslint/no-shadow
for (let i = 0, n = parsedPatterns.length; i < n; i++) {
// Pattern matches path
const parsedPattern = (<ParsedExpressionPattern>parsedPatterns[i]);
if (parsedPattern.requiresSiblings && hasSibling) {
if (!basename) {
basename = paths.basename(path);
}
if (!name) {
name = basename.substr(0, basename.length - paths.extname(path).length);
}
}
const result = parsedPattern(path, basename, name, hasSibling!);
if (result) {
return result;
}
}
return null!;
};
const withBasenames = parsedPatterns.find(pattern => !!(<ParsedStringPattern>pattern).allBasenames);
if (withBasenames) {
resultExpression.allBasenames = (<ParsedStringPattern>withBasenames).allBasenames;
}
const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, <string[]>[]);
if (allPaths.length) {
resultExpression.allPaths = allPaths;
}
return resultExpression;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function parseExpressionPattern(pattern: string, value: any, options: IGlobOptions): (ParsedStringPattern | ParsedExpressionPattern) {
if (value === false) {
return NULL; // pattern is disabled
}
const parsedPattern = parsePattern(pattern, options);
if (parsedPattern === NULL) {
return NULL;
}
// Expression Pattern is <boolean>
if (typeof value === 'boolean') {
return parsedPattern;
}
// Expression Pattern is <SiblingClause>
if (value) {
const when = (<SiblingClause>value).when;
if (typeof when === 'string') {
const result: ParsedExpressionPattern = (path: string, basename: string, name: string, hasSibling: (name: string) => boolean | Promise<boolean>) => {
if (!hasSibling || !parsedPattern(path, basename)) {
return null!;
}
const clausePattern = when.replace('$(basename)', name);
const matched = hasSibling(clausePattern);
return matched instanceof Promise ?
matched.then(m => m ? pattern : null!) :
matched ? pattern : null!;
};
result.requiresSiblings = true;
return result;
}
}
// Expression is Anything
return parsedPattern;
}
function aggregateBasenameMatches(parsedPatterns: (ParsedStringPattern | ParsedExpressionPattern)[], result?: string): (ParsedStringPattern | ParsedExpressionPattern)[] {
const basenamePatterns = parsedPatterns.filter(parsedPattern => !!(<ParsedStringPattern>parsedPattern).basenames);
if (basenamePatterns.length < 2) {
return parsedPatterns;
}
const basenames = basenamePatterns.reduce<string[]>((all, current) => all.concat((<ParsedStringPattern>current).basenames!), []);
let patterns: string[];
if (result) {
patterns = [];
// tslint:disable-next-line:one-variable-per-declaration
for (let i = 0, n = basenames.length; i < n; i++) {
patterns.push(result);
}
} else {
patterns = basenamePatterns.reduce((all, current) => all.concat((<ParsedStringPattern>current).patterns!), <string[]>[]);
}
const aggregate: ParsedStringPattern = function (path, basename): string {
if (!path) {
return null!;
}
if (!basename) {
let i: number;
for (i = path.length; i > 0; i--) {
const ch = path.charCodeAt(i - 1);
if (ch === CharCode.Slash || ch === CharCode.Backslash) {
break;
}
}
basename = path.substr(i);
}
const index = basenames.indexOf(basename);
return index !== -1 ? patterns[index] : null!;
};
aggregate.basenames = basenames;
aggregate.patterns = patterns;
aggregate.allBasenames = basenames;
const aggregatedPatterns = parsedPatterns.filter(parsedPattern => !(<ParsedStringPattern>parsedPattern).basenames);
aggregatedPatterns.push(aggregate);
return aggregatedPatterns;
}