amis-rpc-design/node_modules/monaco-editor/esm/vs/base/common/glob.js
2023-10-07 19:42:30 +08:00

578 lines
23 KiB
JavaScript

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;
}