UNPKG

10.8 kBJavaScriptView Raw
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
\No newline at end of file