Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1948dce

Browse filesBrowse files
MoLownodejs-github-bot
authored andcommitted
fs: add globSync implementation
this is currently for internal use only, with a synchrnous API. PR-URL: #47653 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 71d7707 commit 1948dce
Copy full SHA for 1948dce

File tree

Expand file treeCollapse file tree

2 files changed

+693
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

2 files changed

+693
-0
lines changed
Open diff view settings
Collapse file

‎lib/internal/fs/glob.js‎

Copy file name to clipboard
+384Lines changed: 384 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,384 @@
1+
'use strict';
2+
const { lstatSync, readdirSync } = require('fs');
3+
const { join, resolve } = require('path');
4+
5+
const {
6+
kEmptyObject,
7+
} = require('internal/util');
8+
const {
9+
validateFunction,
10+
validateObject,
11+
} = require('internal/validators');
12+
13+
const {
14+
ArrayFrom,
15+
ArrayPrototypeAt,
16+
ArrayPrototypeMap,
17+
ArrayPrototypeFlatMap,
18+
ArrayPrototypePop,
19+
ArrayPrototypePush,
20+
ArrayPrototypeSome,
21+
SafeMap,
22+
SafeSet,
23+
StringPrototypeEndsWith,
24+
} = primordials;
25+
26+
let minimatch;
27+
function lazyMinimatch() {
28+
minimatch ??= require('internal/deps/minimatch/index');
29+
return minimatch;
30+
}
31+
32+
const isWindows = process.platform === 'win32';
33+
const isOSX = process.platform === 'darwin';
34+
35+
class Cache {
36+
#cache = new SafeMap();
37+
#statsCache = new SafeMap();
38+
#readdirCache = new SafeMap();
39+
40+
statSync(path) {
41+
const cached = this.#statsCache.get(path);
42+
if (cached) {
43+
return cached;
44+
}
45+
let val;
46+
try {
47+
val = lstatSync(path);
48+
} catch {
49+
val = null;
50+
}
51+
this.#statsCache.set(path, val);
52+
return val;
53+
}
54+
addToStatCache(path, val) {
55+
this.#statsCache.set(path, val);
56+
}
57+
readdirSync(path) {
58+
const cached = this.#readdirCache.get(path);
59+
if (cached) {
60+
return cached;
61+
}
62+
let val;
63+
try {
64+
val = readdirSync(path, { __proto__: null, withFileTypes: true });
65+
} catch {
66+
val = [];
67+
}
68+
this.#readdirCache.set(path, val);
69+
return val;
70+
}
71+
add(path, pattern) {
72+
let cache = this.#cache.get(path);
73+
if (!cache) {
74+
cache = new SafeSet();
75+
this.#cache.set(path, cache);
76+
}
77+
const originalSize = cache.size;
78+
pattern.indexes.forEach((index) => cache.add(pattern.cacheKey(index)));
79+
return cache.size !== originalSize + pattern.indexes.size;
80+
}
81+
seen(path, pattern, index) {
82+
return this.#cache.get(path)?.has(pattern.cacheKey(index));
83+
}
84+
}
85+
86+
class Pattern {
87+
#pattern;
88+
#globStrings;
89+
indexes;
90+
symlinks;
91+
last;
92+
93+
constructor(pattern, globStrings, indexes, symlinks) {
94+
this.#pattern = pattern;
95+
this.#globStrings = globStrings;
96+
this.indexes = indexes;
97+
this.symlinks = symlinks;
98+
this.last = pattern.length - 1;
99+
}
100+
101+
isLast(isDirectory) {
102+
return this.indexes.has(this.last) ||
103+
(this.at(-1) === '' && isDirectory &&
104+
this.indexes.has(this.last - 1) && this.at(-2) === lazyMinimatch().GLOBSTAR);
105+
}
106+
isFirst() {
107+
return this.indexes.has(0);
108+
}
109+
get hasSeenSymlinks() {
110+
return ArrayPrototypeSome(ArrayFrom(this.indexes), (i) => !this.symlinks.has(i));
111+
}
112+
at(index) {
113+
return ArrayPrototypeAt(this.#pattern, index);
114+
}
115+
child(indexes, symlinks = new SafeSet()) {
116+
return new Pattern(this.#pattern, this.#globStrings, indexes, symlinks);
117+
}
118+
test(index, path) {
119+
if (index > this.#pattern.length) {
120+
return false;
121+
}
122+
const pattern = this.#pattern[index];
123+
if (pattern === lazyMinimatch().GLOBSTAR) {
124+
return true;
125+
}
126+
if (typeof pattern === 'string') {
127+
return pattern === path;
128+
}
129+
if (typeof pattern?.test === 'function') {
130+
return pattern.test(path);
131+
}
132+
return false;
133+
}
134+
135+
cacheKey(index) {
136+
let key = '';
137+
for (let i = index; i < this.#globStrings.length; i++) {
138+
key += this.#globStrings[i];
139+
if (i !== this.#globStrings.length - 1) {
140+
key += '/';
141+
}
142+
}
143+
return key;
144+
}
145+
}
146+
147+
class Glob {
148+
#root;
149+
#exclude;
150+
#cache = new Cache();
151+
#results = [];
152+
#queue = [];
153+
#subpatterns = new SafeMap();
154+
constructor(patterns, options = kEmptyObject) {
155+
validateObject(options, 'options');
156+
const { exclude, cwd } = options;
157+
if (exclude != null) {
158+
validateFunction(exclude, 'options.exclude');
159+
}
160+
this.#root = cwd ?? '.';
161+
this.#exclude = exclude;
162+
this.matchers = ArrayPrototypeMap(patterns, (pattern) => new (lazyMinimatch().Minimatch)(pattern, {
163+
__proto__: null,
164+
nocase: isWindows || isOSX,
165+
windowsPathsNoEscape: true,
166+
nonegate: true,
167+
nocomment: true,
168+
optimizationLevel: 2,
169+
platform: process.platform,
170+
nocaseMagicOnly: true,
171+
}));
172+
}
173+
174+
globSync() {
175+
ArrayPrototypePush(this.#queue, {
176+
__proto__: null,
177+
path: '.',
178+
patterns: ArrayPrototypeFlatMap(this.matchers, (matcher) => ArrayPrototypeMap(matcher.set,
179+
(pattern, i) => new Pattern(
180+
pattern,
181+
matcher.globParts[i],
182+
new SafeSet([0]),
183+
new SafeSet(),
184+
))),
185+
});
186+
187+
while (this.#queue.length > 0) {
188+
const item = ArrayPrototypePop(this.#queue);
189+
for (let i = 0; i < item.patterns.length; i++) {
190+
this.#addSubpatterns(item.path, item.patterns[i]);
191+
}
192+
this.#subpatterns
193+
.forEach((patterns, path) => ArrayPrototypePush(this.#queue, { __proto__: null, path, patterns }));
194+
this.#subpatterns.clear();
195+
}
196+
return this.#results;
197+
}
198+
#addSubpattern(path, pattern) {
199+
if (!this.#subpatterns.has(path)) {
200+
this.#subpatterns.set(path, [pattern]);
201+
} else {
202+
ArrayPrototypePush(this.#subpatterns.get(path), pattern);
203+
}
204+
}
205+
#addSubpatterns(path, pattern) {
206+
const seen = this.#cache.add(path, pattern);
207+
if (seen) {
208+
return;
209+
}
210+
const fullpath = resolve(this.#root, path);
211+
const stat = this.#cache.statSync(fullpath);
212+
const last = pattern.last;
213+
const isDirectory = stat?.isDirectory() || (stat?.isSymbolicLink() && pattern.hasSeenSymlinks);
214+
const isLast = pattern.isLast(isDirectory);
215+
const isFirst = pattern.isFirst();
216+
217+
if (isFirst && isWindows && typeof pattern.at(0) === 'string' && StringPrototypeEndsWith(pattern.at(0), ':')) {
218+
// Absolute path, go to root
219+
this.#addSubpattern(`${pattern.at(0)}\\`, pattern.child(new SafeSet([1])));
220+
return;
221+
}
222+
if (isFirst && pattern.at(0) === '') {
223+
// Absolute path, go to root
224+
this.#addSubpattern('/', pattern.child(new SafeSet([1])));
225+
return;
226+
}
227+
if (isFirst && pattern.at(0) === '..') {
228+
// Start with .., go to parent
229+
this.#addSubpattern('../', pattern.child(new SafeSet([1])));
230+
return;
231+
}
232+
if (isFirst && pattern.at(0) === '.') {
233+
// Start with ., proceed
234+
this.#addSubpattern('.', pattern.child(new SafeSet([1])));
235+
return;
236+
}
237+
238+
if (isLast && typeof pattern.at(-1) === 'string') {
239+
// Add result if it exists
240+
const p = pattern.at(-1);
241+
const stat = this.#cache.statSync(join(fullpath, p));
242+
if (stat && (p || isDirectory)) {
243+
ArrayPrototypePush(this.#results, join(path, p));
244+
}
245+
if (pattern.indexes.size === 1 && pattern.indexes.has(last)) {
246+
return;
247+
}
248+
} else if (isLast && pattern.at(-1) === lazyMinimatch().GLOBSTAR &&
249+
(path !== '.' || pattern.at(0) === '.' || (last === 0 && stat))) {
250+
// If pattern ends with **, add to results
251+
// if path is ".", add it only if pattern starts with "." or pattern is exactly "**"
252+
ArrayPrototypePush(this.#results, path);
253+
}
254+
255+
if (!isDirectory) {
256+
return;
257+
}
258+
259+
let children;
260+
const firstPattern = pattern.indexes.size === 1 && pattern.at(pattern.indexes.values().next().value);
261+
if (typeof firstPattern === 'string') {
262+
const stat = this.#cache.statSync(join(fullpath, firstPattern));
263+
if (stat) {
264+
stat.name = firstPattern;
265+
children = [stat];
266+
} else {
267+
children = [];
268+
}
269+
} else {
270+
children = this.#cache.readdirSync(fullpath);
271+
}
272+
273+
for (let i = 0; i < children.length; i++) {
274+
const entry = children[i];
275+
const entryPath = join(path, entry.name);
276+
this.#cache.addToStatCache(join(fullpath, entry.name), entry);
277+
278+
const subPatterns = new SafeSet();
279+
const nSymlinks = new SafeSet();
280+
for (const index of pattern.indexes) {
281+
// For each child, chek potential patterns
282+
if (this.#cache.seen(entryPath, pattern, index) || this.#cache.seen(entryPath, pattern, index + 1)) {
283+
return;
284+
}
285+
const current = pattern.at(index);
286+
const nextIndex = index + 1;
287+
const next = pattern.at(nextIndex);
288+
const fromSymlink = pattern.symlinks.has(index);
289+
290+
if (current === lazyMinimatch().GLOBSTAR) {
291+
if (entry.name[0] === '.' || (this.#exclude && this.#exclude(entry.name))) {
292+
continue;
293+
}
294+
if (!fromSymlink && entry.isDirectory()) {
295+
// If directory, add ** to its potential patterns
296+
subPatterns.add(index);
297+
} else if (!fromSymlink && index === last) {
298+
// If ** is last, add to results
299+
ArrayPrototypePush(this.#results, entryPath);
300+
}
301+
302+
// Any pattern after ** is also a potential pattern
303+
// so we can already test it here
304+
const nextMatches = pattern.test(nextIndex, entry.name);
305+
if (nextMatches && nextIndex === last && !isLast) {
306+
// If next pattern is the last one, add to results
307+
ArrayPrototypePush(this.#results, entryPath);
308+
} else if (nextMatches && entry.isDirectory()) {
309+
// Pattern mached, meaning two patterns forward
310+
// are also potential patterns
311+
// e.g **/b/c when entry is a/b - add c to potential patterns
312+
subPatterns.add(index + 2);
313+
}
314+
if ((nextMatches || pattern.at(0) === '.') &&
315+
(entry.isDirectory() || entry.isSymbolicLink()) && !fromSymlink) {
316+
// If pattern after ** matches, or pattern starts with "."
317+
// and entry is a directory or symlink, add to potential patterns
318+
subPatterns.add(nextIndex);
319+
}
320+
321+
if (entry.isSymbolicLink()) {
322+
nSymlinks.add(index);
323+
}
324+
325+
if (next === '..' && entry.isDirectory()) {
326+
// In case pattern is "**/..",
327+
// both parent and current directory should be added to the queue
328+
// if this is the last pattern, add to results instead
329+
const parent = join(path, '..');
330+
if (nextIndex < last) {
331+
if (!this.#subpatterns.has(path) && !this.#cache.seen(path, pattern, nextIndex + 1)) {
332+
this.#subpatterns.set(path, [pattern.child(new SafeSet([nextIndex + 1]))]);
333+
}
334+
if (!this.#subpatterns.has(parent) && !this.#cache.seen(parent, pattern, nextIndex + 1)) {
335+
this.#subpatterns.set(parent, [pattern.child(new SafeSet([nextIndex + 1]))]);
336+
}
337+
} else {
338+
if (!this.#cache.seen(path, pattern, nextIndex)) {
339+
this.#cache.add(path, pattern.child(new SafeSet([nextIndex])));
340+
ArrayPrototypePush(this.#results, path);
341+
}
342+
if (!this.#cache.seen(path, pattern, nextIndex) || !this.#cache.seen(parent, pattern, nextIndex)) {
343+
this.#cache.add(parent, pattern.child(new SafeSet([nextIndex])));
344+
ArrayPrototypePush(this.#results, parent);
345+
}
346+
}
347+
}
348+
}
349+
if (typeof current === 'string') {
350+
if (pattern.test(index, entry.name) && index !== last) {
351+
// If current pattern matches entry name
352+
// the next pattern is a potential pattern
353+
subPatterns.add(nextIndex);
354+
} else if (current === '.' && pattern.test(nextIndex, entry.name)) {
355+
// If current pattern is ".", proceed to test next pattern
356+
if (nextIndex === last) {
357+
ArrayPrototypePush(this.#results, entryPath);
358+
} else {
359+
subPatterns.add(nextIndex + 1);
360+
}
361+
}
362+
}
363+
if (typeof current === 'object' && pattern.test(index, entry.name)) {
364+
// If current pattern is a regex that matches entry name (e.g *.js)
365+
// add next pattern to potential patterns, or to results if it's the last pattern
366+
if (index === last) {
367+
ArrayPrototypePush(this.#results, entryPath);
368+
} else if (entry.isDirectory()) {
369+
subPatterns.add(nextIndex);
370+
}
371+
}
372+
}
373+
if (subPatterns.size > 0) {
374+
// If there are potential patterns, add to queue
375+
this.#addSubpattern(entryPath, pattern.child(subPatterns, nSymlinks));
376+
}
377+
}
378+
}
379+
}
380+
381+
module.exports = {
382+
__proto__: null,
383+
Glob,
384+
};

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.