glob.js 23 KB

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