UNPKG

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