processor.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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 =
  107. hasWalkedCache ? hasWalkedCache.copy() : new HasWalkedCache();
  108. }
  109. processPatterns(target, patterns) {
  110. this.patterns = patterns;
  111. const processingSet = patterns.map(p => [target, p]);
  112. // map of paths to the magic-starting subwalks they need to walk
  113. // first item in patterns is the filter
  114. for (let [t, pattern] of processingSet) {
  115. this.hasWalkedCache.storeWalked(t, pattern);
  116. const root = pattern.root();
  117. const absolute = pattern.isAbsolute() && this.opts.absolute !== false;
  118. // start absolute patterns at root
  119. if (root) {
  120. t = t.resolve(root === '/' && this.opts.root !== undefined ?
  121. this.opts.root
  122. : root);
  123. const rest = pattern.rest();
  124. if (!rest) {
  125. this.matches.add(t, true, false);
  126. continue;
  127. }
  128. else {
  129. pattern = rest;
  130. }
  131. }
  132. if (t.isENOENT())
  133. continue;
  134. let p;
  135. let rest;
  136. let changed = false;
  137. while (typeof (p = pattern.pattern()) === 'string' &&
  138. (rest = pattern.rest())) {
  139. const c = t.resolve(p);
  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 not be final entry, otherwise we would have
  156. // concatenated it earlier.
  157. const ifDir = p === '..' || p === '' || p === '.';
  158. this.matches.add(t.resolve(p), absolute, ifDir);
  159. continue;
  160. }
  161. else if (p === minimatch_1.GLOBSTAR) {
  162. // if no rest, match and subwalk pattern
  163. // if rest, process rest and subwalk pattern
  164. // if it's a symlink, but we didn't get here by way of a
  165. // globstar match (meaning it's the first time THIS globstar
  166. // has traversed a symlink), then we follow it. Otherwise, stop.
  167. if (!t.isSymbolicLink() ||
  168. this.follow ||
  169. pattern.checkFollowGlobstar()) {
  170. this.subwalks.add(t, pattern);
  171. }
  172. const rp = rest?.pattern();
  173. const rrest = rest?.rest();
  174. if (!rest || ((rp === '' || rp === '.') && !rrest)) {
  175. // only HAS to be a dir if it ends in **/ or **/.
  176. // but ending in ** will match files as well.
  177. this.matches.add(t, absolute, rp === '' || rp === '.');
  178. }
  179. else {
  180. if (rp === '..') {
  181. // this would mean you're matching **/.. at the fs root,
  182. // and no thanks, I'm not gonna test that specific case.
  183. /* c8 ignore start */
  184. const tp = t.parent || t;
  185. /* c8 ignore stop */
  186. if (!rrest)
  187. this.matches.add(tp, absolute, true);
  188. else if (!this.hasWalkedCache.hasWalked(tp, rrest)) {
  189. this.subwalks.add(tp, rrest);
  190. }
  191. }
  192. }
  193. }
  194. else if (p instanceof RegExp) {
  195. this.subwalks.add(t, pattern);
  196. }
  197. }
  198. return this;
  199. }
  200. subwalkTargets() {
  201. return this.subwalks.keys();
  202. }
  203. child() {
  204. return new Processor(this.opts, this.hasWalkedCache);
  205. }
  206. // return a new Processor containing the subwalks for each
  207. // child entry, and a set of matches, and
  208. // a hasWalkedCache that's a copy of this one
  209. // then we're going to call
  210. filterEntries(parent, entries) {
  211. const patterns = this.subwalks.get(parent);
  212. // put matches and entry walks into the results processor
  213. const results = this.child();
  214. for (const e of entries) {
  215. for (const pattern of patterns) {
  216. const absolute = pattern.isAbsolute();
  217. const p = pattern.pattern();
  218. const rest = pattern.rest();
  219. if (p === minimatch_1.GLOBSTAR) {
  220. results.testGlobstar(e, pattern, rest, absolute);
  221. }
  222. else if (p instanceof RegExp) {
  223. results.testRegExp(e, p, rest, absolute);
  224. }
  225. else {
  226. results.testString(e, p, rest, absolute);
  227. }
  228. }
  229. }
  230. return results;
  231. }
  232. testGlobstar(e, pattern, rest, absolute) {
  233. if (this.dot || !e.name.startsWith('.')) {
  234. if (!pattern.hasMore()) {
  235. this.matches.add(e, absolute, false);
  236. }
  237. if (e.canReaddir()) {
  238. // if we're in follow mode or it's not a symlink, just keep
  239. // testing the same pattern. If there's more after the globstar,
  240. // then this symlink consumes the globstar. If not, then we can
  241. // follow at most ONE symlink along the way, so we mark it, which
  242. // also checks to ensure that it wasn't already marked.
  243. if (this.follow || !e.isSymbolicLink()) {
  244. this.subwalks.add(e, pattern);
  245. }
  246. else if (e.isSymbolicLink()) {
  247. if (rest && pattern.checkFollowGlobstar()) {
  248. this.subwalks.add(e, rest);
  249. }
  250. else if (pattern.markFollowGlobstar()) {
  251. this.subwalks.add(e, pattern);
  252. }
  253. }
  254. }
  255. }
  256. // if the NEXT thing matches this entry, then also add
  257. // the rest.
  258. if (rest) {
  259. const rp = rest.pattern();
  260. if (typeof rp === 'string' &&
  261. // dots and empty were handled already
  262. rp !== '..' &&
  263. rp !== '' &&
  264. rp !== '.') {
  265. this.testString(e, rp, rest.rest(), absolute);
  266. }
  267. else if (rp === '..') {
  268. /* c8 ignore start */
  269. const ep = e.parent || e;
  270. /* c8 ignore stop */
  271. this.subwalks.add(ep, rest);
  272. }
  273. else if (rp instanceof RegExp) {
  274. this.testRegExp(e, rp, rest.rest(), absolute);
  275. }
  276. }
  277. }
  278. testRegExp(e, p, rest, absolute) {
  279. if (!p.test(e.name))
  280. return;
  281. if (!rest) {
  282. this.matches.add(e, absolute, false);
  283. }
  284. else {
  285. this.subwalks.add(e, rest);
  286. }
  287. }
  288. testString(e, p, rest, absolute) {
  289. // should never happen?
  290. if (!e.isNamed(p))
  291. return;
  292. if (!rest) {
  293. this.matches.add(e, absolute, false);
  294. }
  295. else {
  296. this.subwalks.add(e, rest);
  297. }
  298. }
  299. }
  300. exports.Processor = Processor;
  301. //# sourceMappingURL=processor.js.map