src/vs/base/common/glob.ts

861 LOC · 822 covered · 39 uncovered · 239 ranges · 5474 concepts · 105 introducers · 2658 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- glob.ts ×24
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { equals } from './arrays.js';
7 > import { isThenable } from './async.js';
8 > import { CharCode } from './charCode.js';
9 > import { isEqualOrParent } from './extpath.js';
10 > import { LRUCache } from './map.js';
11 > import { basename, extname, posix, sep } from './path.js';
12 > import { isLinux } from './platform.js';
13 > import { endsWithIgnoreCase, equalsIgnoreCase, escapeRegExpCharacters, ltrim } from './strings.js';
14 >
15 > export interface IRelativePattern {
16 >
17 > /**
18 > * A base file path to which this pattern will be matched against relatively.
19 > */
20 > readonly base: string;
21 >
22 > /**
23 > * A file glob pattern like `*.{ts,js}` that will be matched on file paths
24 > * relative to the base path.
25 > *
26 > * Example: Given a base of `/home/work/folder` and a file path of `/home/work/folder/index.js`,
27 > * the file glob pattern will match on `index.js`.
28 > */
29 > readonly pattern: string;
30 > }
31 >
32 > export interface IExpression {
33 > [pattern: string]: boolean | SiblingClause;
34 > }
35 >
36 > export function getEmptyExpression(): IExpression {
37 return Object.create(null);
38 }
40 > interface SiblingClause {
41 > when: string;
42 > }
43 >
44 > export const GLOBSTAR = '**';
45 > export const GLOB_SPLIT = '/';
46 >
47 > const PATH_REGEX = '[/\\\\]'; // any slash or backslash
48 > const NO_PATH_REGEX = '[^/\\\\]'; // any non-slash and non-backslash
49 > const ALL_FORWARD_SLASHES = /\//g;
50 >
51 > function starsToRegExp(starCount: number, isLastPattern?: boolean): string { glob.ts ×4
52 > switch (starCount) {
53 > case 0:
54 return '';
55 > case 1: glob.ts ×4
56 > return `${NO_PATH_REGEX}*?`; // 1 star matches any number of characters except path separator (/ and \) - non greedy (?) glob.ts ×2
57 > default: glob.ts ×4
58 > // Matches: (Path Sep OR Path Val followed by Path Sep) 0-many times except when it's the last pattern glob.ts ×3
59 > // in which case also matches (Path Sep followed by Path Val)
60 > // Group is non capturing because we don't need to capture at all (?:...)
61 > // Overall we use non-greedy matching because it could be that we match too much
62 > return `(?:${PATH_REGEX}|${NO_PATH_REGEX}+${PATH_REGEX}${isLastPattern ? `|${PATH_REGEX}${NO_PATH_REGEX}+` : ''})*?`;
63 > } glob.ts ×4
64 > }
66 > export function splitGlobAware(pattern: string, splitChar: string): string[] {
67 > if (!pattern) { glob.ts ×8
69 > }
71 > const segments: string[] = [];
72 >
73 > let inBraces = false;
74 > let inBrackets = false;
75 >
76 > let curVal = '';
77 > for (const char of pattern) {
78 > switch (char) {
79 > case splitChar:
80 > if (!inBraces && !inBrackets) { glob.ts ×1
81 > segments.push(curVal); glob.ts ×1
82 > curVal = '';
83 >
84 > continue;
85 > }
86 > break; glob.ts ×1
87 > case '{': glob.ts ×8
88 > inBraces = true; glob.ts ×2
89 > break;
90 > case '}': glob.ts ×8
91 > inBraces = false; glob.ts ×2
92 > break;
93 > case '[': glob.ts ×8
94 > inBrackets = true; glob.ts ×2
95 > break;
96 > case ']': glob.ts ×8
97 > inBrackets = false; glob.ts ×2
98 > break;
99 > } glob.ts ×8
100 >
101 > curVal += char;
102 > }
103 >
104 > // Tail
105 > if (curVal) {
106 > segments.push(curVal); glob.ts ×1
107 > }
108 > glob.ts ×8
109 > return segments;
110 > }
111 > glob.ts ×24
112 > function parseRegExp(pattern: string): string { glob.ts ×19
113 > if (!pattern) {
114 return '';
115 }
116 > glob.ts ×19
117 > let regEx = '';
118 >
119 > // Split up into segments for each slash found
120 > const segments = splitGlobAware(pattern, GLOB_SPLIT);
121 >
122 > // Special case where we only have globstars
123 > if (segments.every(segment => segment === GLOBSTAR)) {
124 > regEx = '.*'; glob.ts ×1
125 > }
126 > glob.ts ×19
127 > // Build regex over segments
128 > else {
129 > let previousSegmentWasGlobStar = false;
130 > segments.forEach((segment, index) => {
131 >
132 > // Treat globstar specially
133 > if (segment === GLOBSTAR) {
134 > glob.ts ×3
135 > // if we have more than one globstar after another, just ignore it
136 > if (previousSegmentWasGlobStar) {
137 > return; glob.ts ×1
138 > }
139 > glob.ts ×3
140 > regEx += starsToRegExp(2, index === segments.length - 1);
141 > }
142 > glob.ts ×19
143 > // Anything else, not globstar
144 > else {
145 >
146 > // States
147 > let inBraces = false;
148 > let braceVal = '';
149 >
150 > let inBrackets = false;
151 > let bracketVal = '';
152 >
153 > for (const char of segment) {
154 >
155 > // Support brace expansion
156 > if (char !== '}' && inBraces) {
157 > braceVal += char; glob.ts ×3
158 > continue;
159 > }
160 > glob.ts ×19
161 > // Support brackets
162 > if (inBrackets && (char !== ']' || !bracketVal) /* ] is literally only allowed as first character in brackets to match it */) {
163 > let res: string; glob.ts ×6
164 >
165 > // range operator
166 > if (char === '-') {
167 > res = char; glob.ts ×1
168 > }
169 > glob.ts ×6
170 > // negation operator (only valid on first index in bracket)
171 > else if ((char === '^' || char === '!') && !bracketVal) {
172 > res = '^'; glob.ts ×2
173 > }
174 > glob.ts ×6
175 > // glob split matching is not allowed within character ranges
176 > // see http://man7.org/linux/man-pages/man7/glob.7.html
177 > else if (char === GLOB_SPLIT) {
178 > res = ''; glob.ts ×2
179 > }
180 > glob.ts ×6
181 > // anything else gets escaped
182 > else {
183 > res = escapeRegExpCharacters(char);
184 > }
185 >
186 > bracketVal += res;
187 > continue;
188 > }
189 > glob.ts ×19
190 > switch (char) {
191 > case '{':
192 > inBraces = true; glob.ts ×3
193 > continue;
194 > glob.ts ×19
195 > case '[':
196 > inBrackets = true; glob.ts ×6
197 > continue;
198 > glob.ts ×19
199 > case '}': {
200 > const choices = splitGlobAware(braceVal, ','); glob.ts ×3
201 >
202 > // Converts {foo,bar} => [foo|bar]
203 > const braceRegExp = `(?:${choices.map(choice => parseRegExp(choice)).join('|')})`;
204 >
205 > regEx += braceRegExp;
206 >
207 > inBraces = false;
208 > braceVal = '';
209 >
210 > break;
211 > }
212 > glob.ts ×19
213 > case ']': {
214 > regEx += ('[' + bracketVal + ']'); glob.ts ×6
215 >
216 > inBrackets = false;
217 > bracketVal = '';
218 >
219 > break;
220 > }
221 > glob.ts ×19
222 > case '?':
223 > regEx += NO_PATH_REGEX; // 1 ? matches any single character except path separator (/ and \) glob.ts ×1
224 > continue;
225 > glob.ts ×19
226 > case '*':
227 > regEx += starsToRegExp(1); glob.ts ×2
228 > continue;
229 > glob.ts ×19
230 > default:
231 > regEx += escapeRegExpCharacters(char); glob.ts ×1
232 > } glob.ts ×19
233 > }
234 >
235 > // Tail: Add the slash we had split on if there is more to
236 > // come and the remaining pattern is not a globstar
237 > // For example if pattern: some/**/*.js we want the "/" after
238 > // some to be included in the RegEx to prevent a folder called
239 > // "something" to match as well.
240 > if (
241 > index < segments.length - 1 && // more segments to come after this
242 > ( glob.ts ×1
243 > segments[index + 1] !== GLOBSTAR || // next segment is not **, or...
244 > index + 2 < segments.length // ...next segment is ** but there is more segments after that glob.ts ×1
245 > ) glob.ts ×19
246 > ) {
247 > regEx += PATH_REGEX; glob.ts ×1
248 > }
249 > } glob.ts ×19
250 >
251 > // update globstar state
252 > previousSegmentWasGlobStar = (segment === GLOBSTAR);
253 > });
254 > }
255 >
256 > return regEx;
257 > }
258 > glob.ts ×24
259 > // regexes to check for trivial glob patterns that just check for String#endsWith
260 > const T1 = /^\*\*\/\*\.[\w\.-]+$/; // **/*.something
261 > const T2 = /^\*\*\/([\w\.-]+)\/?$/; // **/something
262 > const T3 = /^{\*\*\/\*?[\w\.-]+\/?(,\*\*\/\*?[\w\.-]+\/?)*}$/; // {**/*.something,**/*.else} or {**/package.json,**/project.json}
263 > const T3_2 = /^{\*\*\/\*?[\w\.-]+(\/(\*\*)?)?(,\*\*\/\*?[\w\.-]+(\/(\*\*)?)?)*}$/; // Like T3, with optional trailing /**
264 > const T4 = /^\*\*((\/[\w\.-]+)+)\/?$/; // **/something/else
265 > const T5 = /^([\w\.-]+(\/[\w\.-]+)*)\/?$/; // something/else
266 >
267 > export type ParsedPattern = (path: string, basename?: string) => boolean;
268 >
269 > // The `ParsedExpression` returns a `Promise`
270 > // iff `hasSibling` returns a `Promise`.
271 > export type ParsedExpression = (path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise<boolean>) => string | null | Promise<string | null> /* the matching pattern */;
272 >
273 > export interface IGlobOptions {
274 >
275 > /**
276 > * Simplify patterns for use as exclusion filters during
277 > * tree traversal to skip entire subtrees. Cannot be used
278 > * outside of a tree traversal.
279 > */
280 > trimForExclusions?: boolean;
281 >
282 > /**
283 > * Whether glob pattern matching should be case insensitive.
284 > */
285 > ignoreCase?: boolean;
286 > }
287 >
288 > interface IGlobOptionsInternal extends IGlobOptions {
289 > equals: (a: string, b: string) => boolean;
290 > endsWith: (str: string, candidate: string) => boolean;
291 > isEqualOrParent: (base: string, candidate: string) => boolean;
292 > }
293 >
294 > interface ParsedStringPattern {
295 > (path: string, basename?: string): string | null | Promise<string | null> /* the matching pattern */;
296 > basenames?: string[];
297 > patterns?: string[];
298 > allBasenames?: string[];
299 > allPaths?: string[];
300 > }
301 >
302 > interface ParsedExpressionPattern {
303 > (path: string, basename?: string, name?: string, hasSibling?: (name: string) => boolean | Promise<boolean>): string | null | Promise<string | null> /* the matching pattern */;
304 > requiresSiblings?: boolean;
305 > allBasenames?: string[];
306 > allPaths?: string[];
307 > }
308 >
309 > const CACHE = new LRUCache<string, ParsedStringPattern>(10000); // bounded to 10000 elements
310 >
311 > const FALSE = function () {
312 > return false; glob.ts ×2
313 > };
314 > glob.ts ×24
315 > const NULL = function (): string | null {
316 > return null; glob.ts ×1
317 > };
318 > glob.ts ×24
319 > /**
320 > * Check if a provided parsed pattern or expression
321 > * is empty - hence it won't ever match anything.
322 > *
323 > * See {@link FALSE} and {@link NULL}.
324 > */
325 > export function isEmptyPattern(pattern: ParsedPattern | ParsedExpression): pattern is (typeof FALSE | typeof NULL) {
326 > if (pattern === FALSE) { glob.ts ×1
327 > return true;
328 > }
329 >
330 > if (pattern === NULL) {
331 > return true;
332 > }
333
334 return false;
335 }
336 > glob.ts ×24
337 > function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): ParsedStringPattern { glob.ts ×2
338 > if (!arg1) {
339 > return NULL; glob.ts ×2
340 > }
341 > glob.ts ×7
342 > // Handle relative patterns
343 > let pattern: string;
344 > if (typeof arg1 !== 'string') {
345 > pattern = arg1.pattern; glob.ts ×3
346 > } else { glob.ts ×7
347 > pattern = arg1; glob.ts ×2
348 > }
349 > glob.ts ×7
350 > // Whitespace trimming
351 > pattern = pattern.trim();
352 >
353 > const ignoreCase = options.ignoreCase ?? false;
354 > const internalOptions = { glob.ts ×2
355 > ...options,
356 > equals: ignoreCase ? equalsIgnoreCase : (a: string, b: string) => a === b,
357 > endsWith: ignoreCase ? endsWithIgnoreCase : (str: string, candidate: string) => str.endsWith(candidate),
358 > isEqualOrParent: (base: string, candidate: string) => isEqualOrParent(base, candidate, options.ignoreCase ?? !isLinux /* preserve old behaviour for when option is not adopted */)
359 > };
360 >
361 > // Check cache
362 > const patternKey = `${ignoreCase ? pattern.toLowerCase() : pattern}_${!!options.trimForExclusions}_${ignoreCase}`;
363 > let parsedPattern = CACHE.get(patternKey);
364 > if (parsedPattern) {
365 > return wrapRelativePattern(parsedPattern, arg1, internalOptions); glob.ts ×1
366 > }
367 > glob.ts ×7
368 > // Check for Trivials
369 > let match: RegExpExecArray | null;
370 > if (T1.test(pattern)) {
371 > parsedPattern = trivia1(pattern.substring(4), pattern, internalOptions); // common pattern: **/*.txt just need endsWith check glob.ts ×3
372 > } else if (match = T2.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: **/some.txt just need basename check glob.ts ×7
373 > parsedPattern = trivia2(match[1], pattern, internalOptions); glob.ts ×3
374 > } else if ((options.trimForExclusions ? T3_2 : T3).test(pattern)) { // repetition of common patterns (see above) {**/*.txt,**/*.png} glob.ts ×2
375 > parsedPattern = trivia3(pattern, internalOptions); glob.ts ×3
376 > } else if (match = T4.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: **/something/else just need endsWith check glob.ts ×1
377 > parsedPattern = trivia4and5(match[1].substring(1), pattern, true, internalOptions); glob.ts ×2
378 > } else if (match = T5.exec(trimForExclusions(pattern, internalOptions))) { // common pattern: something/else just need equals check glob.ts ×1
379 > parsedPattern = trivia4and5(match[1], pattern, false, internalOptions); glob.ts ×3
380 > }
381 > glob.ts ×19
382 > // Otherwise convert to pattern
383 > else {
384 > parsedPattern = toRegExp(pattern, internalOptions);
385 > }
386 > glob.ts ×7
387 > // Cache
388 > CACHE.set(patternKey, parsedPattern);
389 >
390 > return wrapRelativePattern(parsedPattern, arg1, internalOptions);
391 > }
392 > glob.ts ×24
393 > function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | IRelativePattern, options: IGlobOptionsInternal): ParsedStringPattern { glob.ts ×7
394 > if (typeof arg2 === 'string') {
395 > return parsedPattern; glob.ts ×2
396 > }
397 > glob.ts ×3
398 > const wrappedPattern: ParsedStringPattern = function (path, basename) {
399 > if (!options.isEqualOrParent(path, arg2.base)) {
400 > // skip glob matching if `base` is not a parent of `path` glob.ts ×1
401 > return null;
402 > }
403 > glob.ts ×1
404 > // Given we have checked `base` being a parent of `path`,
405 > // we can now remove the `base` portion of the `path`
406 > // and only match on the remaining path components
407 > // For that we try to extract the portion of the `path`
408 > // that comes after the `base` portion. We have to account
409 > // for the fact that `base` might end in a path separator
410 > // (https://github.com/microsoft/vscode/issues/162498)
411 >
412 > return parsedPattern(ltrim(path.substring(arg2.base.length), sep), basename);
413 > }; glob.ts ×3
414 >
415 > // Make sure to preserve associated metadata
416 > wrappedPattern.allBasenames = parsedPattern.allBasenames;
417 > wrappedPattern.allPaths = parsedPattern.allPaths;
418 > wrappedPattern.basenames = parsedPattern.basenames;
419 > wrappedPattern.patterns = parsedPattern.patterns;
420 >
421 > return wrappedPattern;
422 > }
423 > glob.ts ×24
424 > function trimForExclusions(pattern: string, options: IGlobOptions): string { glob.ts ×2
425 > return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substring(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
426 > }
427 > glob.ts ×24
428 > // common pattern: **/*.txt just need endsWith check
429 > function trivia1(base: string, pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { glob.ts ×3
430 > return function (path: string, basename?: string) {
431 > return typeof path === 'string' && options.endsWith(path, base) ? pattern : null; glob.ts ×1
432 > };
433 > } glob.ts ×3
434 > glob.ts ×24
435 > // common pattern: **/some.txt just need basename check
436 > function trivia2(base: string, pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { glob.ts ×3
437 > const slashBase = `/${base}`;
438 > const backslashBase = `\\${base}`;
439 >
440 > const parsedPattern: ParsedStringPattern = function (path: string, basename?: string) {
441 > if (typeof path !== 'string') { glob.ts ×3
442 > return null; glob.ts ×2
443 > }
444 > glob.ts ×3
445 > if (basename) {
446 > return options.equals(basename, base) ? pattern : null; glob.ts ×1
447 > }
448 > glob.ts ×1
449 > return options.equals(path, base) || options.endsWith(path, slashBase) || options.endsWith(path, backslashBase) ? pattern : null; glob.ts ×3
450 > };
451 > glob.ts ×3
452 > const basenames = [base];
453 > parsedPattern.basenames = basenames;
454 > parsedPattern.patterns = [pattern];
455 > parsedPattern.allBasenames = basenames;
456 >
457 > return parsedPattern;
458 > }
459 > glob.ts ×24
460 > // repetition of common patterns (see above) {**/*.txt,**/*.png}
461 > function trivia3(pattern: string, options: IGlobOptionsInternal): ParsedStringPattern { glob.ts ×3
462 > const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1)
463 > .split(',')
464 > .map(pattern => parsePattern(pattern, options))
465 > .filter(pattern => pattern !== NULL), pattern);
466 >
467 > const patternsLength = parsedPatterns.length;
468 > if (!patternsLength) {
469 return NULL;
470 }
471 > glob.ts ×3
472 > if (patternsLength === 1) {
473 > return parsedPatterns[0]; glob.ts ×2
474 > }
475 > glob.ts ×4
476 > const parsedPattern: ParsedStringPattern = function (path: string, basename?: string) {
477 > for (let i = 0, n = parsedPatterns.length; i < n; i++) {
478 > if (parsedPatterns[i](path, basename)) {
479 > return pattern; glob.ts ×1
480 > }
481 > } glob.ts ×4
482 >
483 > return null;
484 > };
485 >
486 > const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
487 > if (withBasenames) {
488 parsedPattern.allBasenames = withBasenames.allBasenames;
489 }
490 > glob.ts ×4
491 > const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, [] as string[]);
492 > if (allPaths.length) {
493 parsedPattern.allPaths = allPaths;
494 }
495 > glob.ts ×4
496 > return parsedPattern;
497 > }
498 > glob.ts ×24
499 > // common patterns: **/something/else just need endsWith check, something/else just needs and equals check
500 > function trivia4and5(targetPath: string, pattern: string, matchPathEnds: boolean, options: IGlobOptionsInternal): ParsedStringPattern { glob.ts ×3
501 > const usingPosixSep = sep === posix.sep;
502 > const nativePath = usingPosixSep ? targetPath : targetPath.replace(ALL_FORWARD_SLASHES, sep);
503 > const nativePathEnd = sep + nativePath;
504 > const targetPathEnd = posix.sep + targetPath;
505 >
506 > let parsedPattern: ParsedStringPattern;
507 > if (matchPathEnds) {
508 > parsedPattern = function (path: string, basename?: string) { glob.ts ×2
509 > return typeof path === 'string' && ( glob.ts ×2
510 > (options.equals(path, nativePath) || options.endsWith(path, nativePathEnd)) ||
511 > !usingPosixSep && (options.equals(path, targetPath) || options.endsWith(path, targetPathEnd)) glob.ts ×1
512 > ) ? pattern : null; glob.ts ×2
513 > };
514 > } else { glob.ts ×3
515 > parsedPattern = function (path: string, basename?: string) { glob.ts ×3
516 > return typeof path === 'string' && (options.equals(path, nativePath) || (!usingPosixSep && options.equals(path, targetPath))) ? pattern : null; glob.ts ×1
517 > };
518 > } glob.ts ×3
519 > glob.ts ×3
520 > parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + targetPath];
521 >
522 > return parsedPattern;
523 > }
524 > glob.ts ×24
525 > function toRegExp(pattern: string, options: IGlobOptions): ParsedStringPattern { glob.ts ×19
526 > try {
527 > const regExp = new RegExp(`^${parseRegExp(pattern)}$`, options.ignoreCase ? 'i' : undefined);
528 > return function (path: string) {
529 > regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it! glob.ts ×1
530 >
531 > return typeof path === 'string' && regExp.test(path) ? pattern : null;
532 > };
533 > } catch { glob.ts ×19
534 return NULL;
535 }
536 > } glob.ts ×19
537 > glob.ts ×24
538 > /**
539 > * Simplified glob matching. Supports a subset of glob patterns:
540 > * * `*` to match zero or more characters in a path segment
541 > * * `?` to match on one character in a path segment
542 > * * `**` to match any number of path segments, including none
543 > * * `{}` to group conditions (e.g. *.{ts,js} matches all TypeScript and JavaScript files)
544 > * * `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
545 > * * `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
546 > */
547 > export function match(pattern: string | IRelativePattern, path: string, options?: IGlobOptions): boolean;
548 > export function match(expression: IExpression, path: string, options?: IGlobOptions): boolean;
549 > export function match(arg1: string | IExpression | IRelativePattern, path: string, options?: IGlobOptions): boolean {
550 > if (!arg1 || typeof path !== 'string') { glob.ts ×1
551 > return false; glob.ts ×2
552 > }
553 > glob.ts ×1
554 > return parse(arg1, options)(path) as boolean;
555 > }
556 > glob.ts ×24
557 > /**
558 > * Simplified glob matching. Supports a subset of glob patterns:
559 > * * `*` to match zero or more characters in a path segment
560 > * * `?` to match on one character in a path segment
561 > * * `**` to match any number of path segments, including none
562 > * * `{}` to group conditions (e.g. *.{ts,js} matches all TypeScript and JavaScript files)
563 > * * `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
564 > * * `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
565 > */
566 > export function parse(pattern: string | IRelativePattern, options?: IGlobOptions): ParsedPattern;
567 > export function parse(expression: IExpression, options?: IGlobOptions): ParsedExpression;
568 > export function parse(arg1: string | IExpression | IRelativePattern, options?: IGlobOptions): ParsedPattern | ParsedExpression;
569 > export function parse(arg1: string | IExpression | IRelativePattern, options: IGlobOptions = {}): ParsedPattern | ParsedExpression {
570 > if (!arg1) { glob.ts ×2
571 > return FALSE; glob.ts ×1
572 > }
573 > glob.ts ×1
574 > // Glob with String
575 > if (typeof arg1 === 'string' || isRelativePattern(arg1)) { glob.ts ×2
576 > const parsedPattern = parsePattern(arg1, options); glob.ts ×5
577 > if (parsedPattern === NULL) {
578 return FALSE;
579 }
580 > glob.ts ×5
581 > const resultPattern: ParsedPattern & { allBasenames?: string[]; allPaths?: string[] } = function (path: string, basename?: string) {
582 > return !!parsedPattern(path, basename); glob.ts ×1
583 > };
584 > glob.ts ×5
585 > if (parsedPattern.allBasenames) {
586 > resultPattern.allBasenames = parsedPattern.allBasenames; glob.ts ×1
587 > }
588 > glob.ts ×5
589 > if (parsedPattern.allPaths) {
590 > resultPattern.allPaths = parsedPattern.allPaths; glob.ts ×1
591 > }
592 > glob.ts ×5
593 > return resultPattern;
594 > }
595 > glob.ts ×2
596 > // Glob with Expression
597 > return parsedExpression(arg1, options);
598 > }
599 > glob.ts ×24
600 > export function isRelativePattern(obj: unknown): obj is IRelativePattern {
601 > const rp = obj as IRelativePattern | undefined | null; glob.ts ×2
602 > if (!rp) {
603 return false;
604 }
605 > glob.ts ×2
606 > return typeof rp.base === 'string' && typeof rp.pattern === 'string';
607 > }
608 > glob.ts ×24
609 > export function getBasenameTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
610 > return (<ParsedStringPattern>patternOrExpression).allBasenames || []; glob.ts ×1
611 > }
612 > glob.ts ×24
613 > export function getPathTerms(patternOrExpression: ParsedPattern | ParsedExpression): string[] {
614 > return (<ParsedStringPattern>patternOrExpression).allPaths || []; glob.ts ×2
615 > }
616 > glob.ts ×24
617 > function parsedExpression(expression: IExpression, options: IGlobOptions): ParsedExpression { glob.ts ×2
618 > const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
619 > .map(pattern => parseExpressionPattern(pattern, expression[pattern], options))
620 > .filter(pattern => pattern !== NULL));
621 >
622 > const patternsLength = parsedPatterns.length;
623 > if (!patternsLength) {
624 > return NULL; glob.ts ×1
625 > }
626 > glob.ts ×2
627 > if (!parsedPatterns.some(parsedPattern => !!(<ParsedExpressionPattern>parsedPattern).requiresSiblings)) {
628 > if (patternsLength === 1) { glob.ts ×1
629 > return parsedPatterns[0] as ParsedStringPattern; glob.ts ×1
630 > }
631 > glob.ts ×4
632 > const resultExpression: ParsedStringPattern = function (path: string, basename?: string) {
633 > let resultPromises: Promise<string | null>[] | undefined = undefined; glob.ts ×4
634 >
635 > for (let i = 0, n = parsedPatterns.length; i < n; i++) {
636 > const result = parsedPatterns[i](path, basename);
637 > if (typeof result === 'string') {
638 > return result; // immediately return as soon as the first expression matches glob.ts ×1
639 > }
640 > glob.ts ×4
641 > // If the result is a promise, we have to keep it for
642 > // later processing and await the result properly.
643 > if (isThenable(result)) {
644 if (!resultPromises) {
645 resultPromises = [];
646 }
647
648 resultPromises.push(result);
649 }
650 > } glob.ts ×4
651 > glob.ts ×2
652 > // With result promises, we have to loop over each and
653 > // await the result before we can return any result.
654 > if (resultPromises) {
655 return (async () => {
656 for (const resultPromise of resultPromises) {
657 const result = await resultPromise;
658 if (typeof result === 'string') {
659 return result;
660 }
661 }
662
663 return null;
664 })();
665 }
666 > glob.ts ×2
667 > return null;
668 > }; glob.ts ×4
669 > glob.ts ×4
670 > const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
671 > if (withBasenames) {
672 > resultExpression.allBasenames = withBasenames.allBasenames; glob.ts ×1
673 > }
674 > glob.ts ×4
675 > const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, [] as string[]);
676 > if (allPaths.length) {
677 > resultExpression.allPaths = allPaths; glob.ts ×1
678 > }
679 > glob.ts ×4
680 > return resultExpression;
681 > }
682 > glob.ts ×6
683 > const resultExpression: ParsedStringPattern = function (path: string, base?: string, hasSibling?: (name: string) => boolean | Promise<boolean>) {
684 > let name: string | undefined = undefined; glob.ts ×7
685 > let resultPromises: Promise<string | null>[] | undefined = undefined;
686 >
687 > for (let i = 0, n = parsedPatterns.length; i < n; i++) {
688 >
689 > // Pattern matches path
690 > const parsedPattern = (<ParsedExpressionPattern>parsedPatterns[i]);
691 > if (parsedPattern.requiresSiblings && hasSibling) {
692 > if (!base) {
693 > base = basename(path); glob.ts ×1
694 > }
695 > glob.ts ×7
696 > if (!name) {
697 > name = base.substring(0, base.length - extname(path).length);
698 > }
699 > }
700 >
701 > const result = parsedPattern(path, base, name, hasSibling);
702 > if (typeof result === 'string') {
703 > return result; // immediately return as soon as the first expression matches glob.ts ×1
704 > }
705 > glob.ts ×7
706 > // If the result is a promise, we have to keep it for
707 > // later processing and await the result properly.
708 > if (isThenable(result)) {
709 > if (!resultPromises) { glob.ts ×4
710 > resultPromises = [];
711 > }
712 >
713 > resultPromises.push(result);
714 > }
715 > } glob.ts ×7
716 >
717 > // With result promises, we have to loop over each and
718 > // await the result before we can return any result.
719 > if (resultPromises) {
720 > return (async () => { glob.ts ×4
721 > for (const resultPromise of resultPromises) {
722 > const result = await resultPromise;
723 > if (typeof result === 'string') {
724 > return result;
725 > }
726 > }
727 > glob.ts ×1
728 > return null;
729 > })(); glob.ts ×4
730 > }
731 > glob.ts ×1
732 > return null;
733 > }; glob.ts ×7
734 > glob.ts ×6
735 > const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
736 > if (withBasenames) {
737 > resultExpression.allBasenames = withBasenames.allBasenames; glob.ts ×1
738 > }
739 > glob.ts ×6
740 > const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, [] as string[]);
741 > if (allPaths.length) {
742 > resultExpression.allPaths = allPaths; glob.ts ×2
743 > }
744 > glob.ts ×6
745 > return resultExpression;
746 > }
747 > glob.ts ×24
748 > function parseExpressionPattern(pattern: string, value: boolean | SiblingClause, options: IGlobOptions): (ParsedStringPattern | ParsedExpressionPattern) { glob.ts ×1
749 > if (value === false) {
750 > return NULL; // pattern is disabled glob.ts ×1
751 > }
752 > glob.ts ×1
753 > const parsedPattern = parsePattern(pattern, options);
754 > if (parsedPattern === NULL) {
755 > return NULL; glob.ts ×2
756 > }
757 > glob.ts ×2
758 > // Expression Pattern is <boolean>
759 > if (typeof value === 'boolean') {
760 > return parsedPattern; glob.ts ×1
761 > }
762 > glob.ts ×1
763 > // Expression Pattern is <SiblingClause>
764 > if (value) {
765 > const when = value.when; glob.ts ×6
766 > if (typeof when === 'string') {
767 > const result: ParsedExpressionPattern = (path: string, basename?: string, name?: string, hasSibling?: (name: string) => boolean | Promise<boolean>) => {
768 > if (!hasSibling || !parsedPattern(path, basename)) { glob.ts ×7
769 > return null; glob.ts ×1
770 > }
771 > glob.ts ×1
772 > const clausePattern = when.replace('$(basename)', () => name!);
773 > const matched = hasSibling(clausePattern);
774 > return isThenable(matched) ?
775 > matched.then(match => match ? pattern : null) : glob.ts ×4
776 > matched ? pattern : null; glob.ts ×1
777 > }; glob.ts ×7
778 > glob.ts ×6
779 > result.requiresSiblings = true;
780 >
781 > return result;
782 > }
783 > }
784 > glob.ts ×1
785 > // Expression is anything
786 > return parsedPattern;
787 > }
788 > glob.ts ×24
789 > function aggregateBasenameMatches(parsedPatterns: Array<ParsedStringPattern | ParsedExpressionPattern>, result?: string): Array<ParsedStringPattern | ParsedExpressionPattern> { glob.ts ×1
790 > const basenamePatterns = parsedPatterns.filter(parsedPattern => !!(<ParsedStringPattern>parsedPattern).basenames);
791 > if (basenamePatterns.length < 2) {
792 > return parsedPatterns; glob.ts ×1
793 > }
794 > glob.ts ×4
795 > const basenames = basenamePatterns.reduce<string[]>((all, current) => {
796 > const basenames = (<ParsedStringPattern>current).basenames;
797 >
798 > return basenames ? all.concat(basenames) : all;
799 > }, [] as string[]);
800 >
801 > let patterns: string[];
802 > if (result) {
803 > patterns = []; glob.ts ×2
804 >
805 > for (let i = 0, n = basenames.length; i < n; i++) {
806 > patterns.push(result);
807 > }
808 > } else { glob.ts ×4
809 > patterns = basenamePatterns.reduce((all, current) => { glob.ts ×1
810 > const patterns = (<ParsedStringPattern>current).patterns;
811 >
812 > return patterns ? all.concat(patterns) : all;
813 > }, [] as string[]);
814 > }
815 > glob.ts ×4
816 > const aggregate: ParsedStringPattern = function (path: string, basename?: string) {
817 > if (typeof path !== 'string') { glob.ts ×3
818 > return null; glob.ts ×2
819 > }
820 > glob.ts ×3
821 > if (!basename) {
822 > let i: number; glob.ts ×2
823 > for (i = path.length; i > 0; i--) {
824 > const ch = path.charCodeAt(i - 1); glob.ts ×2
825 > if (ch === CharCode.Slash || ch === CharCode.Backslash) {
826 > break; glob.ts ×1
827 > }
828 > } glob.ts ×2
829 > glob.ts ×2
830 > basename = path.substring(i);
831 > }
832 > glob.ts ×3
833 > const index = basenames.indexOf(basename);
834 > return index !== -1 ? patterns[index] : null;
835 > };
836 > glob.ts ×4
837 > aggregate.basenames = basenames;
838 > aggregate.patterns = patterns;
839 > aggregate.allBasenames = basenames;
840 >
841 > const aggregatedPatterns = parsedPatterns.filter(parsedPattern => !(<ParsedStringPattern>parsedPattern).basenames);
842 > aggregatedPatterns.push(aggregate);
843 >
844 > return aggregatedPatterns;
845 > }
846 > glob.ts ×24
847 > // NOTE: This is not used for actual matching, only for resetting watcher when patterns change.
848 > // That is why it's ok to avoid case-insensitive comparison here.
849 > export function patternsEquals(patternsA: Array<string | IRelativePattern> | undefined, patternsB: Array<string | IRelativePattern> | undefined): boolean {
850 > return equals(patternsA, patternsB, (a, b) => { glob.ts ×2
851 > if (typeof a === 'string' && typeof b === 'string') {
852 > return a === b;
853 > }
854 >
855 > if (typeof a !== 'string' && typeof b !== 'string') {
856 > return a.base === b.base && a.pattern === b.pattern;
857 > }
858
859 return false;
860 > }); glob.ts ×2
861 > }