| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
- };
- import { isThenable } from './async.js';
- import { isEqualOrParent } from './extpath.js';
- import { LRUCache } from './map.js';
- import { basename, extname, posix, sep } from './path.js';
- import { isLinux } from './platform.js';
- import { escapeRegExpCharacters, ltrim } from './strings.js';
- export const GLOBSTAR = '**';
- export 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, isLastPattern) {
- 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) 0-many times except when it's the last pattern
- // in which case also matches (Path Sep followed by Path Val)
- // 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}${isLastPattern ? `|${PATH_REGEX}${NO_PATH_REGEX}+` : ''})*?`;
- }
- }
- export function splitGlobAware(pattern, splitChar) {
- if (!pattern) {
- return [];
- }
- const segments = [];
- let inBraces = false;
- let inBrackets = false;
- let curVal = '';
- for (const char of pattern) {
- 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) {
- if (!pattern) {
- return '';
- }
- let regEx = '';
- // Split up into segments for each slash found
- const segments = splitGlobAware(pattern, GLOB_SPLIT);
- // Special case where we only have globstars
- if (segments.every(segment => segment === GLOBSTAR)) {
- regEx = '.*';
- }
- // Build regex over segments
- else {
- let previousSegmentWasGlobStar = false;
- segments.forEach((segment, index) => {
- // Treat globstar specially
- if (segment === GLOBSTAR) {
- // if we have more than one globstar after another, just ignore it
- if (previousSegmentWasGlobStar) {
- return;
- }
- regEx += starsToRegExp(2, index === segments.length - 1);
- }
- // Anything else, not globstar
- else {
- // States
- let inBraces = false;
- let braceVal = '';
- let inBrackets = false;
- let bracketVal = '';
- for (const char of segment) {
- // 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;
- // range operator
- if (char === '-') {
- res = char;
- }
- // negation operator (only valid on first index in bracket)
- 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
- else if (char === GLOB_SPLIT) {
- res = '';
- }
- // anything else gets escaped
- else {
- res = escapeRegExpCharacters(char);
- }
- bracketVal += res;
- continue;
- }
- switch (char) {
- case '{':
- inBraces = true;
- continue;
- case '[':
- inBrackets = true;
- continue;
- case '}': {
- const choices = splitGlobAware(braceVal, ',');
- // Converts {foo,bar} => [foo|bar]
- const braceRegExp = `(?:${choices.map(choice => parseRegExp(choice)).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 += 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.
- if (index < segments.length - 1 && // more segments to come after this
- (segments[index + 1] !== GLOBSTAR || // next segment is not **, or...
- index + 2 < segments.length // ...next segment is ** but there is more segments after that
- )) {
- regEx += PATH_REGEX;
- }
- }
- // update globstar state
- previousSegmentWasGlobStar = (segment === GLOBSTAR);
- });
- }
- 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
- const CACHE = new LRUCache(10000); // bounded to 10000 elements
- const FALSE = function () {
- return false;
- };
- const NULL = function () {
- return null;
- };
- function parsePattern(arg1, options) {
- if (!arg1) {
- return NULL;
- }
- // Handle relative patterns
- let pattern;
- 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 Trivials
- let match;
- if (T1.test(pattern)) {
- parsedPattern = trivia1(pattern.substr(4), pattern); // common pattern: **/*.txt just need endsWith check
- }
- 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
- else {
- parsedPattern = toRegExp(pattern);
- }
- // Cache
- CACHE.set(patternKey, parsedPattern);
- return wrapRelativePattern(parsedPattern, arg1);
- }
- function wrapRelativePattern(parsedPattern, arg2) {
- if (typeof arg2 === 'string') {
- return parsedPattern;
- }
- const wrappedPattern = function (path, basename) {
- if (!isEqualOrParent(path, arg2.base, !isLinux)) {
- // skip glob matching if `base` is not a parent of `path`
- return null;
- }
- // Given we have checked `base` being a parent of `path`,
- // we can now remove the `base` portion of the `path`
- // and only match on the remaining path components
- // For that we try to extract the portion of the `path`
- // that comes after the `base` portion. We have to account
- // for the fact that `base` might end in a path separator
- // (https://github.com/microsoft/vscode/issues/162498)
- return parsedPattern(ltrim(path.substr(arg2.base.length), sep), basename);
- };
- // Make sure to preserve associated metadata
- wrappedPattern.allBasenames = parsedPattern.allBasenames;
- wrappedPattern.allPaths = parsedPattern.allPaths;
- wrappedPattern.basenames = parsedPattern.basenames;
- wrappedPattern.patterns = parsedPattern.patterns;
- return wrappedPattern;
- }
- function trimForExclusions(pattern, options) {
- return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
- }
- // common pattern: **/*.txt just need endsWith check
- function trivia1(base, pattern) {
- return function (path, basename) {
- return typeof path === 'string' && path.endsWith(base) ? pattern : null;
- };
- }
- // common pattern: **/some.txt just need basename check
- function trivia2(base, pattern) {
- const slashBase = `/${base}`;
- const backslashBase = `\\${base}`;
- const parsedPattern = function (path, basename) {
- if (typeof path !== 'string') {
- return null;
- }
- if (basename) {
- return basename === base ? pattern : null;
- }
- return path === base || path.endsWith(slashBase) || path.endsWith(backslashBase) ? pattern : null;
- };
- const basenames = [base];
- parsedPattern.basenames = basenames;
- parsedPattern.patterns = [pattern];
- parsedPattern.allBasenames = basenames;
- return parsedPattern;
- }
- // repetition of common patterns (see above) {**/*.txt,**/*.png}
- function trivia3(pattern, options) {
- const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1)
- .split(',')
- .map(pattern => parsePattern(pattern, options))
- .filter(pattern => pattern !== NULL), pattern);
- const patternsLength = parsedPatterns.length;
- if (!patternsLength) {
- return NULL;
- }
- if (patternsLength === 1) {
- return parsedPatterns[0];
- }
- const parsedPattern = function (path, basename) {
- for (let i = 0, n = parsedPatterns.length; i < n; i++) {
- if (parsedPatterns[i](path, basename)) {
- return pattern;
- }
- }
- return null;
- };
- const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
- if (withBasenames) {
- parsedPattern.allBasenames = withBasenames.allBasenames;
- }
- const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
- 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(targetPath, pattern, matchPathEnds) {
- const usingPosixSep = sep === posix.sep;
- const nativePath = usingPosixSep ? targetPath : targetPath.replace(ALL_FORWARD_SLASHES, sep);
- const nativePathEnd = sep + nativePath;
- const targetPathEnd = posix.sep + targetPath;
- let parsedPattern;
- if (matchPathEnds) {
- parsedPattern = function (path, basename) {
- return typeof path === 'string' && ((path === nativePath || path.endsWith(nativePathEnd)) || !usingPosixSep && (path === targetPath || path.endsWith(targetPathEnd))) ? pattern : null;
- };
- }
- else {
- parsedPattern = function (path, basename) {
- return typeof path === 'string' && (path === nativePath || (!usingPosixSep && path === targetPath)) ? pattern : null;
- };
- }
- parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + targetPath];
- return parsedPattern;
- }
- function toRegExp(pattern) {
- try {
- const regExp = new RegExp(`^${parseRegExp(pattern)}$`);
- return function (path) {
- regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it!
- return typeof path === 'string' && regExp.test(path) ? pattern : null;
- };
- }
- catch (error) {
- return NULL;
- }
- }
- export function match(arg1, path, hasSibling) {
- if (!arg1 || typeof path !== 'string') {
- return false;
- }
- return parse(arg1)(path, undefined, hasSibling);
- }
- export function parse(arg1, options = {}) {
- if (!arg1) {
- return FALSE;
- }
- // Glob with String
- if (typeof arg1 === 'string' || isRelativePattern(arg1)) {
- const parsedPattern = parsePattern(arg1, options);
- if (parsedPattern === NULL) {
- return FALSE;
- }
- const resultPattern = function (path, basename) {
- return !!parsedPattern(path, basename);
- };
- if (parsedPattern.allBasenames) {
- resultPattern.allBasenames = parsedPattern.allBasenames;
- }
- if (parsedPattern.allPaths) {
- resultPattern.allPaths = parsedPattern.allPaths;
- }
- return resultPattern;
- }
- // Glob with Expression
- return parsedExpression(arg1, options);
- }
- export function isRelativePattern(obj) {
- const rp = obj;
- if (!rp) {
- return false;
- }
- return typeof rp.base === 'string' && typeof rp.pattern === 'string';
- }
- function parsedExpression(expression, options) {
- const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
- .map(pattern => parseExpressionPattern(pattern, expression[pattern], options))
- .filter(pattern => pattern !== NULL));
- const patternsLength = parsedPatterns.length;
- if (!patternsLength) {
- return NULL;
- }
- if (!parsedPatterns.some(parsedPattern => !!parsedPattern.requiresSiblings)) {
- if (patternsLength === 1) {
- return parsedPatterns[0];
- }
- const resultExpression = function (path, basename) {
- let resultPromises = undefined;
- for (let i = 0, n = parsedPatterns.length; i < n; i++) {
- const result = parsedPatterns[i](path, basename);
- if (typeof result === 'string') {
- return result; // immediately return as soon as the first expression matches
- }
- // If the result is a promise, we have to keep it for
- // later processing and await the result properly.
- if (isThenable(result)) {
- if (!resultPromises) {
- resultPromises = [];
- }
- resultPromises.push(result);
- }
- }
- // With result promises, we have to loop over each and
- // await the result before we can return any result.
- if (resultPromises) {
- return (() => __awaiter(this, void 0, void 0, function* () {
- for (const resultPromise of resultPromises) {
- const result = yield resultPromise;
- if (typeof result === 'string') {
- return result;
- }
- }
- return null;
- }))();
- }
- return null;
- };
- const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
- if (withBasenames) {
- resultExpression.allBasenames = withBasenames.allBasenames;
- }
- const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
- if (allPaths.length) {
- resultExpression.allPaths = allPaths;
- }
- return resultExpression;
- }
- const resultExpression = function (path, base, hasSibling) {
- let name = undefined;
- let resultPromises = undefined;
- for (let i = 0, n = parsedPatterns.length; i < n; i++) {
- // Pattern matches path
- const parsedPattern = parsedPatterns[i];
- if (parsedPattern.requiresSiblings && hasSibling) {
- if (!base) {
- base = basename(path);
- }
- if (!name) {
- name = base.substr(0, base.length - extname(path).length);
- }
- }
- const result = parsedPattern(path, base, name, hasSibling);
- if (typeof result === 'string') {
- return result; // immediately return as soon as the first expression matches
- }
- // If the result is a promise, we have to keep it for
- // later processing and await the result properly.
- if (isThenable(result)) {
- if (!resultPromises) {
- resultPromises = [];
- }
- resultPromises.push(result);
- }
- }
- // With result promises, we have to loop over each and
- // await the result before we can return any result.
- if (resultPromises) {
- return (() => __awaiter(this, void 0, void 0, function* () {
- for (const resultPromise of resultPromises) {
- const result = yield resultPromise;
- if (typeof result === 'string') {
- return result;
- }
- }
- return null;
- }))();
- }
- return null;
- };
- const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
- if (withBasenames) {
- resultExpression.allBasenames = withBasenames.allBasenames;
- }
- const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
- if (allPaths.length) {
- resultExpression.allPaths = allPaths;
- }
- return resultExpression;
- }
- function parseExpressionPattern(pattern, value, options) {
- 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 = value.when;
- if (typeof when === 'string') {
- const result = (path, basename, name, hasSibling) => {
- if (!hasSibling || !parsedPattern(path, basename)) {
- return null;
- }
- const clausePattern = when.replace('$(basename)', () => name);
- const matched = hasSibling(clausePattern);
- return isThenable(matched) ?
- matched.then(match => match ? pattern : null) :
- matched ? pattern : null;
- };
- result.requiresSiblings = true;
- return result;
- }
- }
- // Expression is anything
- return parsedPattern;
- }
- function aggregateBasenameMatches(parsedPatterns, result) {
- const basenamePatterns = parsedPatterns.filter(parsedPattern => !!parsedPattern.basenames);
- if (basenamePatterns.length < 2) {
- return parsedPatterns;
- }
- const basenames = basenamePatterns.reduce((all, current) => {
- const basenames = current.basenames;
- return basenames ? all.concat(basenames) : all;
- }, []);
- let patterns;
- if (result) {
- patterns = [];
- for (let i = 0, n = basenames.length; i < n; i++) {
- patterns.push(result);
- }
- }
- else {
- patterns = basenamePatterns.reduce((all, current) => {
- const patterns = current.patterns;
- return patterns ? all.concat(patterns) : all;
- }, []);
- }
- const aggregate = function (path, basename) {
- if (typeof path !== 'string') {
- return null;
- }
- if (!basename) {
- let i;
- for (i = path.length; i > 0; i--) {
- const ch = path.charCodeAt(i - 1);
- if (ch === 47 /* CharCode.Slash */ || ch === 92 /* 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 => !parsedPattern.basenames);
- aggregatedPatterns.push(aggregate);
- return aggregatedPatterns;
- }
|