walker.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.GlobStream = exports.GlobWalker = exports.GlobUtil = void 0;
  4. /**
  5. * Single-use utility classes to provide functionality to the {@link Glob}
  6. * methods.
  7. *
  8. * @module
  9. */
  10. const minipass_1 = require("minipass");
  11. const ignore_js_1 = require("./ignore.js");
  12. const processor_js_1 = require("./processor.js");
  13. const makeIgnore = (ignore, opts) => typeof ignore === 'string' ? new ignore_js_1.Ignore([ignore], opts)
  14. : Array.isArray(ignore) ? new ignore_js_1.Ignore(ignore, opts)
  15. : ignore;
  16. /**
  17. * basic walking utilities that all the glob walker types use
  18. */
  19. class GlobUtil {
  20. path;
  21. patterns;
  22. opts;
  23. seen = new Set();
  24. paused = false;
  25. aborted = false;
  26. #onResume = [];
  27. #ignore;
  28. #sep;
  29. signal;
  30. maxDepth;
  31. includeChildMatches;
  32. constructor(patterns, path, opts) {
  33. this.patterns = patterns;
  34. this.path = path;
  35. this.opts = opts;
  36. this.#sep = !opts.posix && opts.platform === 'win32' ? '\\' : '/';
  37. this.includeChildMatches = opts.includeChildMatches !== false;
  38. if (opts.ignore || !this.includeChildMatches) {
  39. this.#ignore = makeIgnore(opts.ignore ?? [], opts);
  40. if (!this.includeChildMatches &&
  41. typeof this.#ignore.add !== 'function') {
  42. const m = 'cannot ignore child matches, ignore lacks add() method.';
  43. throw new Error(m);
  44. }
  45. }
  46. // ignore, always set with maxDepth, but it's optional on the
  47. // GlobOptions type
  48. /* c8 ignore start */
  49. this.maxDepth = opts.maxDepth || Infinity;
  50. /* c8 ignore stop */
  51. if (opts.signal) {
  52. this.signal = opts.signal;
  53. this.signal.addEventListener('abort', () => {
  54. this.#onResume.length = 0;
  55. });
  56. }
  57. }
  58. #ignored(path) {
  59. return this.seen.has(path) || !!this.#ignore?.ignored?.(path);
  60. }
  61. #childrenIgnored(path) {
  62. return !!this.#ignore?.childrenIgnored?.(path);
  63. }
  64. // backpressure mechanism
  65. pause() {
  66. this.paused = true;
  67. }
  68. resume() {
  69. /* c8 ignore start */
  70. if (this.signal?.aborted)
  71. return;
  72. /* c8 ignore stop */
  73. this.paused = false;
  74. let fn = undefined;
  75. while (!this.paused && (fn = this.#onResume.shift())) {
  76. fn();
  77. }
  78. }
  79. onResume(fn) {
  80. if (this.signal?.aborted)
  81. return;
  82. /* c8 ignore start */
  83. if (!this.paused) {
  84. fn();
  85. }
  86. else {
  87. /* c8 ignore stop */
  88. this.#onResume.push(fn);
  89. }
  90. }
  91. // do the requisite realpath/stat checking, and return the path
  92. // to add or undefined to filter it out.
  93. async matchCheck(e, ifDir) {
  94. if (ifDir && this.opts.nodir)
  95. return undefined;
  96. let rpc;
  97. if (this.opts.realpath) {
  98. rpc = e.realpathCached() || (await e.realpath());
  99. if (!rpc)
  100. return undefined;
  101. e = rpc;
  102. }
  103. const needStat = e.isUnknown() || this.opts.stat;
  104. const s = needStat ? await e.lstat() : e;
  105. if (this.opts.follow && this.opts.nodir && s?.isSymbolicLink()) {
  106. const target = await s.realpath();
  107. /* c8 ignore start */
  108. if (target && (target.isUnknown() || this.opts.stat)) {
  109. await target.lstat();
  110. }
  111. /* c8 ignore stop */
  112. }
  113. return this.matchCheckTest(s, ifDir);
  114. }
  115. matchCheckTest(e, ifDir) {
  116. return (e &&
  117. (this.maxDepth === Infinity || e.depth() <= this.maxDepth) &&
  118. (!ifDir || e.canReaddir()) &&
  119. (!this.opts.nodir || !e.isDirectory()) &&
  120. (!this.opts.nodir ||
  121. !this.opts.follow ||
  122. !e.isSymbolicLink() ||
  123. !e.realpathCached()?.isDirectory()) &&
  124. !this.#ignored(e)) ?
  125. e
  126. : undefined;
  127. }
  128. matchCheckSync(e, ifDir) {
  129. if (ifDir && this.opts.nodir)
  130. return undefined;
  131. let rpc;
  132. if (this.opts.realpath) {
  133. rpc = e.realpathCached() || e.realpathSync();
  134. if (!rpc)
  135. return undefined;
  136. e = rpc;
  137. }
  138. const needStat = e.isUnknown() || this.opts.stat;
  139. const s = needStat ? e.lstatSync() : e;
  140. if (this.opts.follow && this.opts.nodir && s?.isSymbolicLink()) {
  141. const target = s.realpathSync();
  142. if (target && (target?.isUnknown() || this.opts.stat)) {
  143. target.lstatSync();
  144. }
  145. }
  146. return this.matchCheckTest(s, ifDir);
  147. }
  148. matchFinish(e, absolute) {
  149. if (this.#ignored(e))
  150. return;
  151. // we know we have an ignore if this is false, but TS doesn't
  152. if (!this.includeChildMatches && this.#ignore?.add) {
  153. const ign = `${e.relativePosix()}/**`;
  154. this.#ignore.add(ign);
  155. }
  156. const abs = this.opts.absolute === undefined ? absolute : this.opts.absolute;
  157. this.seen.add(e);
  158. const mark = this.opts.mark && e.isDirectory() ? this.#sep : '';
  159. // ok, we have what we need!
  160. if (this.opts.withFileTypes) {
  161. this.matchEmit(e);
  162. }
  163. else if (abs) {
  164. const abs = this.opts.posix ? e.fullpathPosix() : e.fullpath();
  165. this.matchEmit(abs + mark);
  166. }
  167. else {
  168. const rel = this.opts.posix ? e.relativePosix() : e.relative();
  169. const pre = this.opts.dotRelative && !rel.startsWith('..' + this.#sep) ?
  170. '.' + this.#sep
  171. : '';
  172. this.matchEmit(!rel ? '.' + mark : pre + rel + mark);
  173. }
  174. }
  175. async match(e, absolute, ifDir) {
  176. const p = await this.matchCheck(e, ifDir);
  177. if (p)
  178. this.matchFinish(p, absolute);
  179. }
  180. matchSync(e, absolute, ifDir) {
  181. const p = this.matchCheckSync(e, ifDir);
  182. if (p)
  183. this.matchFinish(p, absolute);
  184. }
  185. walkCB(target, patterns, cb) {
  186. /* c8 ignore start */
  187. if (this.signal?.aborted)
  188. cb();
  189. /* c8 ignore stop */
  190. this.walkCB2(target, patterns, new processor_js_1.Processor(this.opts), cb);
  191. }
  192. walkCB2(target, patterns, processor, cb) {
  193. if (this.#childrenIgnored(target))
  194. return cb();
  195. if (this.signal?.aborted)
  196. cb();
  197. if (this.paused) {
  198. this.onResume(() => this.walkCB2(target, patterns, processor, cb));
  199. return;
  200. }
  201. processor.processPatterns(target, patterns);
  202. // done processing. all of the above is sync, can be abstracted out.
  203. // subwalks is a map of paths to the entry filters they need
  204. // matches is a map of paths to [absolute, ifDir] tuples.
  205. let tasks = 1;
  206. const next = () => {
  207. if (--tasks === 0)
  208. cb();
  209. };
  210. for (const [m, absolute, ifDir] of processor.matches.entries()) {
  211. if (this.#ignored(m))
  212. continue;
  213. tasks++;
  214. this.match(m, absolute, ifDir).then(() => next());
  215. }
  216. for (const t of processor.subwalkTargets()) {
  217. if (this.maxDepth !== Infinity && t.depth() >= this.maxDepth) {
  218. continue;
  219. }
  220. tasks++;
  221. const childrenCached = t.readdirCached();
  222. if (t.calledReaddir())
  223. this.walkCB3(t, childrenCached, processor, next);
  224. else {
  225. t.readdirCB((_, entries) => this.walkCB3(t, entries, processor, next), true);
  226. }
  227. }
  228. next();
  229. }
  230. walkCB3(target, entries, processor, cb) {
  231. processor = processor.filterEntries(target, entries);
  232. let tasks = 1;
  233. const next = () => {
  234. if (--tasks === 0)
  235. cb();
  236. };
  237. for (const [m, absolute, ifDir] of processor.matches.entries()) {
  238. if (this.#ignored(m))
  239. continue;
  240. tasks++;
  241. this.match(m, absolute, ifDir).then(() => next());
  242. }
  243. for (const [target, patterns] of processor.subwalks.entries()) {
  244. tasks++;
  245. this.walkCB2(target, patterns, processor.child(), next);
  246. }
  247. next();
  248. }
  249. walkCBSync(target, patterns, cb) {
  250. /* c8 ignore start */
  251. if (this.signal?.aborted)
  252. cb();
  253. /* c8 ignore stop */
  254. this.walkCB2Sync(target, patterns, new processor_js_1.Processor(this.opts), cb);
  255. }
  256. walkCB2Sync(target, patterns, processor, cb) {
  257. if (this.#childrenIgnored(target))
  258. return cb();
  259. if (this.signal?.aborted)
  260. cb();
  261. if (this.paused) {
  262. this.onResume(() => this.walkCB2Sync(target, patterns, processor, cb));
  263. return;
  264. }
  265. processor.processPatterns(target, patterns);
  266. // done processing. all of the above is sync, can be abstracted out.
  267. // subwalks is a map of paths to the entry filters they need
  268. // matches is a map of paths to [absolute, ifDir] tuples.
  269. let tasks = 1;
  270. const next = () => {
  271. if (--tasks === 0)
  272. cb();
  273. };
  274. for (const [m, absolute, ifDir] of processor.matches.entries()) {
  275. if (this.#ignored(m))
  276. continue;
  277. this.matchSync(m, absolute, ifDir);
  278. }
  279. for (const t of processor.subwalkTargets()) {
  280. if (this.maxDepth !== Infinity && t.depth() >= this.maxDepth) {
  281. continue;
  282. }
  283. tasks++;
  284. const children = t.readdirSync();
  285. this.walkCB3Sync(t, children, processor, next);
  286. }
  287. next();
  288. }
  289. walkCB3Sync(target, entries, processor, cb) {
  290. processor = processor.filterEntries(target, entries);
  291. let tasks = 1;
  292. const next = () => {
  293. if (--tasks === 0)
  294. cb();
  295. };
  296. for (const [m, absolute, ifDir] of processor.matches.entries()) {
  297. if (this.#ignored(m))
  298. continue;
  299. this.matchSync(m, absolute, ifDir);
  300. }
  301. for (const [target, patterns] of processor.subwalks.entries()) {
  302. tasks++;
  303. this.walkCB2Sync(target, patterns, processor.child(), next);
  304. }
  305. next();
  306. }
  307. }
  308. exports.GlobUtil = GlobUtil;
  309. class GlobWalker extends GlobUtil {
  310. matches = new Set();
  311. constructor(patterns, path, opts) {
  312. super(patterns, path, opts);
  313. }
  314. matchEmit(e) {
  315. this.matches.add(e);
  316. }
  317. async walk() {
  318. if (this.signal?.aborted)
  319. throw this.signal.reason;
  320. if (this.path.isUnknown()) {
  321. await this.path.lstat();
  322. }
  323. await new Promise((res, rej) => {
  324. this.walkCB(this.path, this.patterns, () => {
  325. if (this.signal?.aborted) {
  326. rej(this.signal.reason);
  327. }
  328. else {
  329. res(this.matches);
  330. }
  331. });
  332. });
  333. return this.matches;
  334. }
  335. walkSync() {
  336. if (this.signal?.aborted)
  337. throw this.signal.reason;
  338. if (this.path.isUnknown()) {
  339. this.path.lstatSync();
  340. }
  341. // nothing for the callback to do, because this never pauses
  342. this.walkCBSync(this.path, this.patterns, () => {
  343. if (this.signal?.aborted)
  344. throw this.signal.reason;
  345. });
  346. return this.matches;
  347. }
  348. }
  349. exports.GlobWalker = GlobWalker;
  350. class GlobStream extends GlobUtil {
  351. results;
  352. constructor(patterns, path, opts) {
  353. super(patterns, path, opts);
  354. this.results = new minipass_1.Minipass({
  355. signal: this.signal,
  356. objectMode: true,
  357. });
  358. this.results.on('drain', () => this.resume());
  359. this.results.on('resume', () => this.resume());
  360. }
  361. matchEmit(e) {
  362. this.results.write(e);
  363. if (!this.results.flowing)
  364. this.pause();
  365. }
  366. stream() {
  367. const target = this.path;
  368. if (target.isUnknown()) {
  369. target.lstat().then(() => {
  370. this.walkCB(target, this.patterns, () => this.results.end());
  371. });
  372. }
  373. else {
  374. this.walkCB(target, this.patterns, () => this.results.end());
  375. }
  376. return this.results;
  377. }
  378. streamSync() {
  379. if (this.path.isUnknown()) {
  380. this.path.lstatSync();
  381. }
  382. this.walkCBSync(this.path, this.patterns, () => this.results.end());
  383. return this.results;
  384. }
  385. }
  386. exports.GlobStream = GlobStream;
  387. //# sourceMappingURL=walker.js.map