glob.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  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 } 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. return parsedPattern(path.substr(arg2.base.length + 1), basename);
  254. };
  255. // Make sure to preserve associated metadata
  256. wrappedPattern.allBasenames = parsedPattern.allBasenames;
  257. wrappedPattern.allPaths = parsedPattern.allPaths;
  258. wrappedPattern.basenames = parsedPattern.basenames;
  259. wrappedPattern.patterns = parsedPattern.patterns;
  260. return wrappedPattern;
  261. }
  262. function trimForExclusions(pattern, options) {
  263. return options.trimForExclusions && pattern.endsWith('/**') ? pattern.substr(0, pattern.length - 2) : pattern; // dropping **, tailing / is dropped later
  264. }
  265. // common pattern: **/*.txt just need endsWith check
  266. function trivia1(base, pattern) {
  267. return function (path, basename) {
  268. return typeof path === 'string' && path.endsWith(base) ? pattern : null;
  269. };
  270. }
  271. // common pattern: **/some.txt just need basename check
  272. function trivia2(base, pattern) {
  273. const slashBase = `/${base}`;
  274. const backslashBase = `\\${base}`;
  275. const parsedPattern = function (path, basename) {
  276. if (typeof path !== 'string') {
  277. return null;
  278. }
  279. if (basename) {
  280. return basename === base ? pattern : null;
  281. }
  282. return path === base || path.endsWith(slashBase) || path.endsWith(backslashBase) ? pattern : null;
  283. };
  284. const basenames = [base];
  285. parsedPattern.basenames = basenames;
  286. parsedPattern.patterns = [pattern];
  287. parsedPattern.allBasenames = basenames;
  288. return parsedPattern;
  289. }
  290. // repetition of common patterns (see above) {**/*.txt,**/*.png}
  291. function trivia3(pattern, options) {
  292. const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1)
  293. .split(',')
  294. .map(pattern => parsePattern(pattern, options))
  295. .filter(pattern => pattern !== NULL), pattern);
  296. const patternsLength = parsedPatterns.length;
  297. if (!patternsLength) {
  298. return NULL;
  299. }
  300. if (patternsLength === 1) {
  301. return parsedPatterns[0];
  302. }
  303. const parsedPattern = function (path, basename) {
  304. for (let i = 0, n = parsedPatterns.length; i < n; i++) {
  305. if (parsedPatterns[i](path, basename)) {
  306. return pattern;
  307. }
  308. }
  309. return null;
  310. };
  311. const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
  312. if (withBasenames) {
  313. parsedPattern.allBasenames = withBasenames.allBasenames;
  314. }
  315. const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
  316. if (allPaths.length) {
  317. parsedPattern.allPaths = allPaths;
  318. }
  319. return parsedPattern;
  320. }
  321. // common patterns: **/something/else just need endsWith check, something/else just needs and equals check
  322. function trivia4and5(targetPath, pattern, matchPathEnds) {
  323. const usingPosixSep = sep === posix.sep;
  324. const nativePath = usingPosixSep ? targetPath : targetPath.replace(ALL_FORWARD_SLASHES, sep);
  325. const nativePathEnd = sep + nativePath;
  326. const targetPathEnd = posix.sep + targetPath;
  327. let parsedPattern;
  328. if (matchPathEnds) {
  329. parsedPattern = function (path, basename) {
  330. return typeof path === 'string' && ((path === nativePath || path.endsWith(nativePathEnd)) || !usingPosixSep && (path === targetPath || path.endsWith(targetPathEnd))) ? pattern : null;
  331. };
  332. }
  333. else {
  334. parsedPattern = function (path, basename) {
  335. return typeof path === 'string' && (path === nativePath || (!usingPosixSep && path === targetPath)) ? pattern : null;
  336. };
  337. }
  338. parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + targetPath];
  339. return parsedPattern;
  340. }
  341. function toRegExp(pattern) {
  342. try {
  343. const regExp = new RegExp(`^${parseRegExp(pattern)}$`);
  344. return function (path) {
  345. regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it!
  346. return typeof path === 'string' && regExp.test(path) ? pattern : null;
  347. };
  348. }
  349. catch (error) {
  350. return NULL;
  351. }
  352. }
  353. export function match(arg1, path, hasSibling) {
  354. if (!arg1 || typeof path !== 'string') {
  355. return false;
  356. }
  357. return parse(arg1)(path, undefined, hasSibling);
  358. }
  359. export function parse(arg1, options = {}) {
  360. if (!arg1) {
  361. return FALSE;
  362. }
  363. // Glob with String
  364. if (typeof arg1 === 'string' || isRelativePattern(arg1)) {
  365. const parsedPattern = parsePattern(arg1, options);
  366. if (parsedPattern === NULL) {
  367. return FALSE;
  368. }
  369. const resultPattern = function (path, basename) {
  370. return !!parsedPattern(path, basename);
  371. };
  372. if (parsedPattern.allBasenames) {
  373. resultPattern.allBasenames = parsedPattern.allBasenames;
  374. }
  375. if (parsedPattern.allPaths) {
  376. resultPattern.allPaths = parsedPattern.allPaths;
  377. }
  378. return resultPattern;
  379. }
  380. // Glob with Expression
  381. return parsedExpression(arg1, options);
  382. }
  383. export function isRelativePattern(obj) {
  384. const rp = obj;
  385. if (!rp) {
  386. return false;
  387. }
  388. return typeof rp.base === 'string' && typeof rp.pattern === 'string';
  389. }
  390. function parsedExpression(expression, options) {
  391. const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression)
  392. .map(pattern => parseExpressionPattern(pattern, expression[pattern], options))
  393. .filter(pattern => pattern !== NULL));
  394. const patternsLength = parsedPatterns.length;
  395. if (!patternsLength) {
  396. return NULL;
  397. }
  398. if (!parsedPatterns.some(parsedPattern => !!parsedPattern.requiresSiblings)) {
  399. if (patternsLength === 1) {
  400. return parsedPatterns[0];
  401. }
  402. const resultExpression = function (path, basename) {
  403. let resultPromises = undefined;
  404. for (let i = 0, n = parsedPatterns.length; i < n; i++) {
  405. const result = parsedPatterns[i](path, basename);
  406. if (typeof result === 'string') {
  407. return result; // immediately return as soon as the first expression matches
  408. }
  409. // If the result is a promise, we have to keep it for
  410. // later processing and await the result properly.
  411. if (isThenable(result)) {
  412. if (!resultPromises) {
  413. resultPromises = [];
  414. }
  415. resultPromises.push(result);
  416. }
  417. }
  418. // With result promises, we have to loop over each and
  419. // await the result before we can return any result.
  420. if (resultPromises) {
  421. return (() => __awaiter(this, void 0, void 0, function* () {
  422. for (const resultPromise of resultPromises) {
  423. const result = yield resultPromise;
  424. if (typeof result === 'string') {
  425. return result;
  426. }
  427. }
  428. return null;
  429. }))();
  430. }
  431. return null;
  432. };
  433. const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
  434. if (withBasenames) {
  435. resultExpression.allBasenames = withBasenames.allBasenames;
  436. }
  437. const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
  438. if (allPaths.length) {
  439. resultExpression.allPaths = allPaths;
  440. }
  441. return resultExpression;
  442. }
  443. const resultExpression = function (path, base, hasSibling) {
  444. let name = undefined;
  445. let resultPromises = undefined;
  446. for (let i = 0, n = parsedPatterns.length; i < n; i++) {
  447. // Pattern matches path
  448. const parsedPattern = parsedPatterns[i];
  449. if (parsedPattern.requiresSiblings && hasSibling) {
  450. if (!base) {
  451. base = basename(path);
  452. }
  453. if (!name) {
  454. name = base.substr(0, base.length - extname(path).length);
  455. }
  456. }
  457. const result = parsedPattern(path, base, name, hasSibling);
  458. if (typeof result === 'string') {
  459. return result; // immediately return as soon as the first expression matches
  460. }
  461. // If the result is a promise, we have to keep it for
  462. // later processing and await the result properly.
  463. if (isThenable(result)) {
  464. if (!resultPromises) {
  465. resultPromises = [];
  466. }
  467. resultPromises.push(result);
  468. }
  469. }
  470. // With result promises, we have to loop over each and
  471. // await the result before we can return any result.
  472. if (resultPromises) {
  473. return (() => __awaiter(this, void 0, void 0, function* () {
  474. for (const resultPromise of resultPromises) {
  475. const result = yield resultPromise;
  476. if (typeof result === 'string') {
  477. return result;
  478. }
  479. }
  480. return null;
  481. }))();
  482. }
  483. return null;
  484. };
  485. const withBasenames = parsedPatterns.find(pattern => !!pattern.allBasenames);
  486. if (withBasenames) {
  487. resultExpression.allBasenames = withBasenames.allBasenames;
  488. }
  489. const allPaths = parsedPatterns.reduce((all, current) => current.allPaths ? all.concat(current.allPaths) : all, []);
  490. if (allPaths.length) {
  491. resultExpression.allPaths = allPaths;
  492. }
  493. return resultExpression;
  494. }
  495. function parseExpressionPattern(pattern, value, options) {
  496. if (value === false) {
  497. return NULL; // pattern is disabled
  498. }
  499. const parsedPattern = parsePattern(pattern, options);
  500. if (parsedPattern === NULL) {
  501. return NULL;
  502. }
  503. // Expression Pattern is <boolean>
  504. if (typeof value === 'boolean') {
  505. return parsedPattern;
  506. }
  507. // Expression Pattern is <SiblingClause>
  508. if (value) {
  509. const when = value.when;
  510. if (typeof when === 'string') {
  511. const result = (path, basename, name, hasSibling) => {
  512. if (!hasSibling || !parsedPattern(path, basename)) {
  513. return null;
  514. }
  515. const clausePattern = when.replace('$(basename)', name);
  516. const matched = hasSibling(clausePattern);
  517. return isThenable(matched) ?
  518. matched.then(match => match ? pattern : null) :
  519. matched ? pattern : null;
  520. };
  521. result.requiresSiblings = true;
  522. return result;
  523. }
  524. }
  525. // Expression is anything
  526. return parsedPattern;
  527. }
  528. function aggregateBasenameMatches(parsedPatterns, result) {
  529. const basenamePatterns = parsedPatterns.filter(parsedPattern => !!parsedPattern.basenames);
  530. if (basenamePatterns.length < 2) {
  531. return parsedPatterns;
  532. }
  533. const basenames = basenamePatterns.reduce((all, current) => {
  534. const basenames = current.basenames;
  535. return basenames ? all.concat(basenames) : all;
  536. }, []);
  537. let patterns;
  538. if (result) {
  539. patterns = [];
  540. for (let i = 0, n = basenames.length; i < n; i++) {
  541. patterns.push(result);
  542. }
  543. }
  544. else {
  545. patterns = basenamePatterns.reduce((all, current) => {
  546. const patterns = current.patterns;
  547. return patterns ? all.concat(patterns) : all;
  548. }, []);
  549. }
  550. const aggregate = function (path, basename) {
  551. if (typeof path !== 'string') {
  552. return null;
  553. }
  554. if (!basename) {
  555. let i;
  556. for (i = path.length; i > 0; i--) {
  557. const ch = path.charCodeAt(i - 1);
  558. if (ch === 47 /* CharCode.Slash */ || ch === 92 /* CharCode.Backslash */) {
  559. break;
  560. }
  561. }
  562. basename = path.substr(i);
  563. }
  564. const index = basenames.indexOf(basename);
  565. return index !== -1 ? patterns[index] : null;
  566. };
  567. aggregate.basenames = basenames;
  568. aggregate.patterns = patterns;
  569. aggregate.allBasenames = basenames;
  570. const aggregatedPatterns = parsedPatterns.filter(parsedPattern => !parsedPattern.basenames);
  571. aggregatedPatterns.push(aggregate);
  572. return aggregatedPatterns;
  573. }