processor.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. "use strict";
  2. // synchronous utility for filtering entries and calculating subwalks
  3. Object.defineProperty(exports, "__esModule", { value: true });
  4. exports.Processor = exports.SubWalks = exports.MatchRecord = exports.HasWalkedCache = void 0;
  5. const minimatch_1 = require("minimatch");
  6. /**
  7. * A cache of which patterns have been processed for a given Path
  8. */
  9. class HasWalkedCache {
  10. store;
  11. constructor(store = new Map()) {
  12. this.store = store;
  13. }
  14. copy() {
  15. return new HasWalkedCache(new Map(this.store));
  16. }
  17. hasWalked(target, pattern) {
  18. return this.store.get(target.fullpath())?.has(pattern.globString());
  19. }
  20. storeWalked(target, pattern) {
  21. const fullpath = target.fullpath();
  22. const cached = this.store.get(fullpath);
  23. if (cached)
  24. cached.add(pattern.globString());
  25. else
  26. this.store.set(fullpath, new Set([pattern.globString()]));
  27. }
  28. }
  29. exports.HasWalkedCache = HasWalkedCache;
  30. /**
  31. * A record of which paths have been matched in a given walk step,
  32. * and whether they only are considered a match if they are a directory,
  33. * and whether their absolute or relative path should be returned.
  34. */
  35. class MatchRecord {
  36. store = new Map();
  37. add(target, absolute, ifDir) {
  38. const n = (absolute ? 2 : 0) | (ifDir ? 1 : 0);
  39. const current = this.store.get(target);
  40. this.store.set(target, current === undefined ? n : n & current);
  41. }
  42. // match, absolute, ifdir
  43. entries() {
  44. return [...this.store.entries()].map(([path, n]) => [
  45. path,
  46. !!(n & 2),
  47. !!(n & 1),
  48. ]);
  49. }
  50. }
  51. exports.MatchRecord = MatchRecord;
  52. /**
  53. * A collection of patterns that must be processed in a subsequent step
  54. * for a given path.
  55. */
  56. class SubWalks {
  57. store = new Map();
  58. add(target, pattern) {
  59. if (!target.canReaddir()) {
  60. return;
  61. }
  62. const subs = this.store.get(target);
  63. if (subs) {
  64. if (!subs.find(p => p.globString() === pattern.globString())) {
  65. subs.push(pattern);
  66. }
  67. }
  68. else
  69. this.store.set(target, [pattern]);
  70. }
  71. get(target) {
  72. const subs = this.store.get(target);
  73. /* c8 ignore start */
  74. if (!subs) {
  75. throw new Error('attempting to walk unknown path');
  76. }
  77. /* c8 ignore stop */
  78. return subs;
  79. }
  80. entries() {
  81. return this.keys().map(k => [k, this.store.get(k)]);
  82. }
  83. keys() {
  84. return [...this.store.keys()].filter(t => t.canReaddir());
  85. }
  86. }
  87. exports.SubWalks = SubWalks;
  88. /**
  89. * The class that processes patterns for a given path.
  90. *
  91. * Handles child entry filtering, and determining whether a path's
  92. * directory contents must be read.
  93. */
  94. class Processor {
  95. hasWalkedCache;
  96. matches = new MatchRecord();
  97. subwalks = new SubWalks();
  98. patterns;
  99. follow;
  100. dot;
  101. opts;
  102. constructor(opts, hasWalkedCache) {
  103. this.opts = opts;
  104. this.follow = !!opts.follow;
  105. this.dot = !!opts.dot;
  106. this.hasWalkedCache = hasWalkedCache
  107. ? hasWalkedCache.copy()
  108. : new HasWalkedCache();
  109. }
  110. processPatterns(target, patterns) {
  111. this.patterns = patterns;
  112. const processingSet = patterns.map(p => [target, p]);
  113. // map of paths to the magic-starting subwalks they need to walk
  114. // first item in patterns is the filter
  115. for (let [t, pattern] of processingSet) {
  116. this.hasWalkedCache.storeWalked(t, pattern);
  117. const root = pattern.root();
  118. const absolute = pattern.isAbsolute();
  119. // start absolute patterns at root
  120. if (root) {
  121. t = t.resolve(root);
  122. const rest = pattern.rest();
  123. if (!rest) {
  124. this.matches.add(t, true, false);
  125. continue;
  126. }
  127. else {
  128. pattern = rest;
  129. }
  130. }
  131. let p;
  132. let rest;
  133. let changed = false;
  134. while (typeof (p = pattern.pattern()) === 'string' &&
  135. (rest = pattern.rest())) {
  136. const c = t.resolve(p);
  137. // we can be reasonably sure that .. is a readable dir
  138. if (c.isUnknown() && p !== '..')
  139. break;
  140. t = c;
  141. pattern = rest;
  142. changed = true;
  143. }
  144. p = pattern.pattern();
  145. rest = pattern.rest();
  146. if (changed) {
  147. if (this.hasWalkedCache.hasWalked(t, pattern))
  148. continue;
  149. this.hasWalkedCache.storeWalked(t, pattern);
  150. }
  151. // now we have either a final string for a known entry,
  152. // more strings for an unknown entry,
  153. // or a pattern starting with magic, mounted on t.
  154. if (typeof p === 'string') {
  155. // must be final entry
  156. if (!rest) {
  157. const ifDir = p === '..' || p === '' || p === '.';
  158. this.matches.add(t.resolve(p), absolute, ifDir);
  159. }
  160. else {
  161. this.subwalks.add(t, pattern);
  162. }
  163. continue;
  164. }
  165. else if (p === minimatch_1.GLOBSTAR) {
  166. // if no rest, match and subwalk pattern
  167. // if rest, process rest and subwalk pattern
  168. // if it's a symlink, but we didn't get here by way of a
  169. // globstar match (meaning it's the first time THIS globstar
  170. // has traversed a symlink), then we follow it. Otherwise, stop.
  171. if (!t.isSymbolicLink() ||
  172. this.follow ||
  173. pattern.checkFollowGlobstar()) {
  174. this.subwalks.add(t, pattern);
  175. }
  176. const rp = rest?.pattern();
  177. const rrest = rest?.rest();
  178. if (!rest || ((rp === '' || rp === '.') && !rrest)) {
  179. // only HAS to be a dir if it ends in **/ or **/.
  180. // but ending in ** will match files as well.
  181. this.matches.add(t, absolute, rp === '' || rp === '.');
  182. }
  183. else {
  184. if (rp === '..') {
  185. // this would mean you're matching **/.. at the fs root,
  186. // and no thanks, I'm not gonna test that specific case.
  187. /* c8 ignore start */
  188. const tp = t.parent || t;
  189. /* c8 ignore stop */
  190. if (!rrest)
  191. this.matches.add(tp, absolute, true);
  192. else if (!this.hasWalkedCache.hasWalked(tp, rrest)) {
  193. this.subwalks.add(tp, rrest);
  194. }
  195. }
  196. }
  197. }
  198. else if (p instanceof RegExp) {
  199. this.subwalks.add(t, pattern);
  200. }
  201. }
  202. return this;
  203. }
  204. subwalkTargets() {
  205. return this.subwalks.keys();
  206. }
  207. child() {
  208. return new Processor(this.opts, this.hasWalkedCache);
  209. }
  210. // return a new Processor containing the subwalks for each
  211. // child entry, and a set of matches, and
  212. // a hasWalkedCache that's a copy of this one
  213. // then we're going to call
  214. filterEntries(parent, entries) {
  215. const patterns = this.subwalks.get(parent);
  216. // put matches and entry walks into the results processor
  217. const results = this.child();
  218. for (const e of entries) {
  219. for (const pattern of patterns) {
  220. const absolute = pattern.isAbsolute();
  221. const p = pattern.pattern();
  222. const rest = pattern.rest();
  223. if (p === minimatch_1.GLOBSTAR) {
  224. results.testGlobstar(e, pattern, rest, absolute);
  225. }
  226. else if (p instanceof RegExp) {
  227. results.testRegExp(e, p, rest, absolute);
  228. }
  229. else {
  230. results.testString(e, p, rest, absolute);
  231. }
  232. }
  233. }
  234. return results;
  235. }
  236. testGlobstar(e, pattern, rest, absolute) {
  237. if (this.dot || !e.name.startsWith('.')) {
  238. if (!pattern.hasMore()) {
  239. this.matches.add(e, absolute, false);
  240. }
  241. if (e.canReaddir()) {
  242. // if we're in follow mode or it's not a symlink, just keep
  243. // testing the same pattern. If there's more after the globstar,
  244. // then this symlink consumes the globstar. If not, then we can
  245. // follow at most ONE symlink along the way, so we mark it, which
  246. // also checks to ensure that it wasn't already marked.
  247. if (this.follow || !e.isSymbolicLink()) {
  248. this.subwalks.add(e, pattern);
  249. }
  250. else if (e.isSymbolicLink()) {
  251. if (rest && pattern.checkFollowGlobstar()) {
  252. this.subwalks.add(e, rest);
  253. }
  254. else if (pattern.markFollowGlobstar()) {
  255. this.subwalks.add(e, pattern);
  256. }
  257. }
  258. }
  259. }
  260. // if the NEXT thing matches this entry, then also add
  261. // the rest.
  262. if (rest) {
  263. const rp = rest.pattern();
  264. if (typeof rp === 'string' &&
  265. // dots and empty were handled already
  266. rp !== '..' &&
  267. rp !== '' &&
  268. rp !== '.') {
  269. this.testString(e, rp, rest.rest(), absolute);
  270. }
  271. else if (rp === '..') {
  272. /* c8 ignore start */
  273. const ep = e.parent || e;
  274. /* c8 ignore stop */
  275. this.subwalks.add(ep, rest);
  276. }
  277. else if (rp instanceof RegExp) {
  278. this.testRegExp(e, rp, rest.rest(), absolute);
  279. }
  280. }
  281. }
  282. testRegExp(e, p, rest, absolute) {
  283. if (!p.test(e.name))
  284. return;
  285. if (!rest) {
  286. this.matches.add(e, absolute, false);
  287. }
  288. else {
  289. this.subwalks.add(e, rest);
  290. }
  291. }
  292. testString(e, p, rest, absolute) {
  293. // should never happen?
  294. if (!e.isNamed(p))
  295. return;
  296. if (!rest) {
  297. this.matches.add(e, absolute, false);
  298. }
  299. else {
  300. this.subwalks.add(e, rest);
  301. }
  302. }
  303. }
  304. exports.Processor = Processor;
  305. //# sourceMappingURL=processor.js.map