1"use strict"; 2// synchronous utility for filtering entries and calculating subwalks 3Object.defineProperty(exports, "__esModule", { value: true }); 4exports.Processor = exports.SubWalks = exports.MatchRecord = exports.HasWalkedCache = void 0; 5const minimatch_1 = require("minimatch"); 6/** 7 * A cache of which patterns have been processed for a given Path 8 */ 9class 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} 29exports.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 */ 35class 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} 51exports.MatchRecord = MatchRecord; 52/** 53 * A collection of patterns that must be processed in a subsequent step 54 * for a given path. 55 */ 56class 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} 87exports.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 */ 94class 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() && this.opts.absolute !== false; 119 // start absolute patterns at root 120 if (root) { 121 t = t.resolve(root === '/' && this.opts.root !== undefined 122 ? this.opts.root 123 : root); 124 const rest = pattern.rest(); 125 if (!rest) { 126 this.matches.add(t, true, false); 127 continue; 128 } 129 else { 130 pattern = rest; 131 } 132 } 133 if (t.isENOENT()) 134 continue; 135 let p; 136 let rest; 137 let changed = false; 138 while (typeof (p = pattern.pattern()) === 'string' && 139 (rest = pattern.rest())) { 140 const c = t.resolve(p); 141 t = c; 142 pattern = rest; 143 changed = true; 144 } 145 p = pattern.pattern(); 146 rest = pattern.rest(); 147 if (changed) { 148 if (this.hasWalkedCache.hasWalked(t, pattern)) 149 continue; 150 this.hasWalkedCache.storeWalked(t, pattern); 151 } 152 // now we have either a final string for a known entry, 153 // more strings for an unknown entry, 154 // or a pattern starting with magic, mounted on t. 155 if (typeof p === 'string') { 156 // must not be final entry, otherwise we would have 157 // concatenated it earlier. 158 const ifDir = p === '..' || p === '' || p === '.'; 159 this.matches.add(t.resolve(p), absolute, ifDir); 160 continue; 161 } 162 else if (p === minimatch_1.GLOBSTAR) { 163 // if no rest, match and subwalk pattern 164 // if rest, process rest and subwalk pattern 165 // if it's a symlink, but we didn't get here by way of a 166 // globstar match (meaning it's the first time THIS globstar 167 // has traversed a symlink), then we follow it. Otherwise, stop. 168 if (!t.isSymbolicLink() || 169 this.follow || 170 pattern.checkFollowGlobstar()) { 171 this.subwalks.add(t, pattern); 172 } 173 const rp = rest?.pattern(); 174 const rrest = rest?.rest(); 175 if (!rest || ((rp === '' || rp === '.') && !rrest)) { 176 // only HAS to be a dir if it ends in **/ or **/. 177 // but ending in ** will match files as well. 178 this.matches.add(t, absolute, rp === '' || rp === '.'); 179 } 180 else { 181 if (rp === '..') { 182 // this would mean you're matching **/.. at the fs root, 183 // and no thanks, I'm not gonna test that specific case. 184 /* c8 ignore start */ 185 const tp = t.parent || t; 186 /* c8 ignore stop */ 187 if (!rrest) 188 this.matches.add(tp, absolute, true); 189 else if (!this.hasWalkedCache.hasWalked(tp, rrest)) { 190 this.subwalks.add(tp, rrest); 191 } 192 } 193 } 194 } 195 else if (p instanceof RegExp) { 196 this.subwalks.add(t, pattern); 197 } 198 } 199 return this; 200 } 201 subwalkTargets() { 202 return this.subwalks.keys(); 203 } 204 child() { 205 return new Processor(this.opts, this.hasWalkedCache); 206 } 207 // return a new Processor containing the subwalks for each 208 // child entry, and a set of matches, and 209 // a hasWalkedCache that's a copy of this one 210 // then we're going to call 211 filterEntries(parent, entries) { 212 const patterns = this.subwalks.get(parent); 213 // put matches and entry walks into the results processor 214 const results = this.child(); 215 for (const e of entries) { 216 for (const pattern of patterns) { 217 const absolute = pattern.isAbsolute(); 218 const p = pattern.pattern(); 219 const rest = pattern.rest(); 220 if (p === minimatch_1.GLOBSTAR) { 221 results.testGlobstar(e, pattern, rest, absolute); 222 } 223 else if (p instanceof RegExp) { 224 results.testRegExp(e, p, rest, absolute); 225 } 226 else { 227 results.testString(e, p, rest, absolute); 228 } 229 } 230 } 231 return results; 232 } 233 testGlobstar(e, pattern, rest, absolute) { 234 if (this.dot || !e.name.startsWith('.')) { 235 if (!pattern.hasMore()) { 236 this.matches.add(e, absolute, false); 237 } 238 if (e.canReaddir()) { 239 // if we're in follow mode or it's not a symlink, just keep 240 // testing the same pattern. If there's more after the globstar, 241 // then this symlink consumes the globstar. If not, then we can 242 // follow at most ONE symlink along the way, so we mark it, which 243 // also checks to ensure that it wasn't already marked. 244 if (this.follow || !e.isSymbolicLink()) { 245 this.subwalks.add(e, pattern); 246 } 247 else if (e.isSymbolicLink()) { 248 if (rest && pattern.checkFollowGlobstar()) { 249 this.subwalks.add(e, rest); 250 } 251 else if (pattern.markFollowGlobstar()) { 252 this.subwalks.add(e, pattern); 253 } 254 } 255 } 256 } 257 // if the NEXT thing matches this entry, then also add 258 // the rest. 259 if (rest) { 260 const rp = rest.pattern(); 261 if (typeof rp === 'string' && 262 // dots and empty were handled already 263 rp !== '..' && 264 rp !== '' && 265 rp !== '.') { 266 this.testString(e, rp, rest.rest(), absolute); 267 } 268 else if (rp === '..') { 269 /* c8 ignore start */ 270 const ep = e.parent || e; 271 /* c8 ignore stop */ 272 this.subwalks.add(ep, rest); 273 } 274 else if (rp instanceof RegExp) { 275 this.testRegExp(e, rp, rest.rest(), absolute); 276 } 277 } 278 } 279 testRegExp(e, p, rest, absolute) { 280 if (!p.test(e.name)) 281 return; 282 if (!rest) { 283 this.matches.add(e, absolute, false); 284 } 285 else { 286 this.subwalks.add(e, rest); 287 } 288 } 289 testString(e, p, rest, absolute) { 290 // should never happen? 291 if (!e.isNamed(p)) 292 return; 293 if (!rest) { 294 this.matches.add(e, absolute, false); 295 } 296 else { 297 this.subwalks.add(e, rest); 298 } 299 } 300} 301exports.Processor = Processor; 302//# sourceMappingURL=processor.js.map