Total Complexity | 123 |
Complexity/F | 7.69 |
Lines of Code | 486 |
Function Count | 16 |
Duplicated Lines | 2 |
Ratio | 0.41 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like node_modules/glob/sync.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | module.exports = globSync |
||
2 | globSync.GlobSync = GlobSync |
||
3 | |||
4 | var fs = require('fs') |
||
5 | var rp = require('fs.realpath') |
||
6 | var minimatch = require('minimatch') |
||
7 | var Minimatch = minimatch.Minimatch |
||
8 | var Glob = require('./glob.js').Glob |
||
9 | var util = require('util') |
||
10 | var path = require('path') |
||
11 | var assert = require('assert') |
||
12 | var isAbsolute = require('path-is-absolute') |
||
13 | var common = require('./common.js') |
||
14 | var alphasort = common.alphasort |
||
15 | var alphasorti = common.alphasorti |
||
16 | var setopts = common.setopts |
||
17 | var ownProp = common.ownProp |
||
18 | var childrenIgnored = common.childrenIgnored |
||
19 | var isIgnored = common.isIgnored |
||
20 | |||
21 | function globSync (pattern, options) { |
||
22 | if (typeof options === 'function' || arguments.length === 3) |
||
23 | throw new TypeError('callback provided to sync glob\n'+ |
||
|
|||
24 | 'See: https://github.com/isaacs/node-glob/issues/167') |
||
25 | |||
26 | return new GlobSync(pattern, options).found |
||
27 | } |
||
28 | |||
29 | function GlobSync (pattern, options) { |
||
30 | if (!pattern) |
||
31 | throw new Error('must provide pattern') |
||
32 | |||
33 | if (typeof options === 'function' || arguments.length === 3) |
||
34 | throw new TypeError('callback provided to sync glob\n'+ |
||
35 | 'See: https://github.com/isaacs/node-glob/issues/167') |
||
36 | |||
37 | if (!(this instanceof GlobSync)) |
||
38 | return new GlobSync(pattern, options) |
||
39 | |||
40 | setopts(this, pattern, options) |
||
41 | |||
42 | if (this.noprocess) |
||
43 | return this |
||
44 | |||
45 | var n = this.minimatch.set.length |
||
46 | this.matches = new Array(n) |
||
47 | for (var i = 0; i < n; i ++) { |
||
48 | this._process(this.minimatch.set[i], i, false) |
||
49 | } |
||
50 | this._finish() |
||
51 | } |
||
52 | |||
53 | GlobSync.prototype._finish = function () { |
||
54 | assert(this instanceof GlobSync) |
||
55 | if (this.realpath) { |
||
56 | var self = this |
||
57 | this.matches.forEach(function (matchset, index) { |
||
58 | var set = self.matches[index] = Object.create(null) |
||
59 | for (var p in matchset) { |
||
60 | try { |
||
61 | p = self._makeAbs(p) |
||
62 | var real = rp.realpathSync(p, self.realpathCache) |
||
63 | set[real] = true |
||
64 | } catch (er) { |
||
65 | if (er.syscall === 'stat') |
||
66 | set[self._makeAbs(p)] = true |
||
67 | else |
||
68 | throw er |
||
69 | } |
||
70 | } |
||
71 | }) |
||
72 | } |
||
73 | common.finish(this) |
||
74 | } |
||
75 | |||
76 | |||
77 | GlobSync.prototype._process = function (pattern, index, inGlobStar) { |
||
78 | assert(this instanceof GlobSync) |
||
79 | |||
80 | // Get the first [n] parts of pattern that are all strings. |
||
81 | var n = 0 |
||
82 | while (typeof pattern[n] === 'string') { |
||
83 | n ++ |
||
84 | } |
||
85 | // now n is the index of the first one that is *not* a string. |
||
86 | |||
87 | // See if there's anything else |
||
88 | var prefix |
||
89 | switch (n) { |
||
90 | // if not, then this is rather simple |
||
91 | case pattern.length: |
||
92 | this._processSimple(pattern.join('/'), index) |
||
93 | return |
||
94 | |||
95 | case 0: |
||
96 | // pattern *starts* with some non-trivial item. |
||
97 | // going to readdir(cwd), but not include the prefix in matches. |
||
98 | prefix = null |
||
99 | break |
||
100 | |||
101 | default: |
||
102 | // pattern has some string bits in the front. |
||
103 | // whatever it starts with, whether that's 'absolute' like /foo/bar, |
||
104 | // or 'relative' like '../baz' |
||
105 | prefix = pattern.slice(0, n).join('/') |
||
106 | break |
||
107 | } |
||
108 | |||
109 | var remain = pattern.slice(n) |
||
110 | |||
111 | // get the list of entries. |
||
112 | var read |
||
113 | if (prefix === null) |
||
114 | read = '.' |
||
115 | else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { |
||
116 | if (!prefix || !isAbsolute(prefix)) |
||
117 | prefix = '/' + prefix |
||
118 | read = prefix |
||
119 | } else |
||
120 | read = prefix |
||
121 | |||
122 | var abs = this._makeAbs(read) |
||
123 | |||
124 | //if ignored, skip processing |
||
125 | if (childrenIgnored(this, read)) |
||
126 | return |
||
127 | |||
128 | var isGlobStar = remain[0] === minimatch.GLOBSTAR |
||
129 | if (isGlobStar) |
||
130 | this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) |
||
131 | else |
||
132 | this._processReaddir(prefix, read, abs, remain, index, inGlobStar) |
||
133 | } |
||
134 | |||
135 | |||
136 | GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { |
||
137 | var entries = this._readdir(abs, inGlobStar) |
||
138 | |||
139 | // if the abs isn't a dir, then nothing can match! |
||
140 | if (!entries) |
||
141 | return |
||
142 | |||
143 | // It will only match dot entries if it starts with a dot, or if |
||
144 | // dot is set. Stuff like @(.foo|.bar) isn't allowed. |
||
145 | var pn = remain[0] |
||
146 | var negate = !!this.minimatch.negate |
||
147 | var rawGlob = pn._glob |
||
148 | var dotOk = this.dot || rawGlob.charAt(0) === '.' |
||
149 | |||
150 | var matchedEntries = [] |
||
151 | View Code Duplication | for (var i = 0; i < entries.length; i++) { |
|
152 | var e = entries[i] |
||
153 | if (e.charAt(0) !== '.' || dotOk) { |
||
154 | var m |
||
155 | if (negate && !prefix) { |
||
156 | m = !e.match(pn) |
||
157 | } else { |
||
158 | m = e.match(pn) |
||
159 | } |
||
160 | if (m) |
||
161 | matchedEntries.push(e) |
||
162 | } |
||
163 | } |
||
164 | |||
165 | var len = matchedEntries.length |
||
166 | // If there are no matched entries, then nothing matches. |
||
167 | if (len === 0) |
||
168 | return |
||
169 | |||
170 | // if this is the last remaining pattern bit, then no need for |
||
171 | // an additional stat *unless* the user has specified mark or |
||
172 | // stat explicitly. We know they exist, since readdir returned |
||
173 | // them. |
||
174 | |||
175 | if (remain.length === 1 && !this.mark && !this.stat) { |
||
176 | if (!this.matches[index]) |
||
177 | this.matches[index] = Object.create(null) |
||
178 | |||
179 | for (var i = 0; i < len; i ++) { |
||
180 | var e = matchedEntries[i] |
||
181 | if (prefix) { |
||
182 | if (prefix.slice(-1) !== '/') |
||
183 | e = prefix + '/' + e |
||
184 | else |
||
185 | e = prefix + e |
||
186 | } |
||
187 | |||
188 | if (e.charAt(0) === '/' && !this.nomount) { |
||
189 | e = path.join(this.root, e) |
||
190 | } |
||
191 | this._emitMatch(index, e) |
||
192 | } |
||
193 | // This was the last one, and no stats were needed |
||
194 | return |
||
195 | } |
||
196 | |||
197 | // now test all matched entries as stand-ins for that part |
||
198 | // of the pattern. |
||
199 | remain.shift() |
||
200 | for (var i = 0; i < len; i ++) { |
||
201 | var e = matchedEntries[i] |
||
202 | var newPattern |
||
203 | if (prefix) |
||
204 | newPattern = [prefix, e] |
||
205 | else |
||
206 | newPattern = [e] |
||
207 | this._process(newPattern.concat(remain), index, inGlobStar) |
||
208 | } |
||
209 | } |
||
210 | |||
211 | |||
212 | GlobSync.prototype._emitMatch = function (index, e) { |
||
213 | if (isIgnored(this, e)) |
||
214 | return |
||
215 | |||
216 | var abs = this._makeAbs(e) |
||
217 | |||
218 | if (this.mark) |
||
219 | e = this._mark(e) |
||
220 | |||
221 | if (this.absolute) { |
||
222 | e = abs |
||
223 | } |
||
224 | |||
225 | if (this.matches[index][e]) |
||
226 | return |
||
227 | |||
228 | if (this.nodir) { |
||
229 | var c = this.cache[abs] |
||
230 | if (c === 'DIR' || Array.isArray(c)) |
||
231 | return |
||
232 | } |
||
233 | |||
234 | this.matches[index][e] = true |
||
235 | |||
236 | if (this.stat) |
||
237 | this._stat(e) |
||
238 | } |
||
239 | |||
240 | |||
241 | GlobSync.prototype._readdirInGlobStar = function (abs) { |
||
242 | // follow all symlinked directories forever |
||
243 | // just proceed as if this is a non-globstar situation |
||
244 | if (this.follow) |
||
245 | return this._readdir(abs, false) |
||
246 | |||
247 | var entries |
||
248 | var lstat |
||
249 | var stat |
||
250 | try { |
||
251 | lstat = fs.lstatSync(abs) |
||
252 | } catch (er) { |
||
253 | if (er.code === 'ENOENT') { |
||
254 | // lstat failed, doesn't exist |
||
255 | return null |
||
256 | } |
||
257 | } |
||
258 | |||
259 | var isSym = lstat && lstat.isSymbolicLink() |
||
260 | this.symlinks[abs] = isSym |
||
261 | |||
262 | // If it's not a symlink or a dir, then it's definitely a regular file. |
||
263 | // don't bother doing a readdir in that case. |
||
264 | if (!isSym && lstat && !lstat.isDirectory()) |
||
265 | this.cache[abs] = 'FILE' |
||
266 | else |
||
267 | entries = this._readdir(abs, false) |
||
268 | |||
269 | return entries |
||
270 | } |
||
271 | |||
272 | GlobSync.prototype._readdir = function (abs, inGlobStar) { |
||
273 | var entries |
||
274 | |||
275 | if (inGlobStar && !ownProp(this.symlinks, abs)) |
||
276 | return this._readdirInGlobStar(abs) |
||
277 | |||
278 | if (ownProp(this.cache, abs)) { |
||
279 | var c = this.cache[abs] |
||
280 | if (!c || c === 'FILE') |
||
281 | return null |
||
282 | |||
283 | if (Array.isArray(c)) |
||
284 | return c |
||
285 | } |
||
286 | |||
287 | try { |
||
288 | return this._readdirEntries(abs, fs.readdirSync(abs)) |
||
289 | } catch (er) { |
||
290 | this._readdirError(abs, er) |
||
291 | return null |
||
292 | } |
||
293 | } |
||
294 | |||
295 | GlobSync.prototype._readdirEntries = function (abs, entries) { |
||
296 | // if we haven't asked to stat everything, then just |
||
297 | // assume that everything in there exists, so we can avoid |
||
298 | // having to stat it a second time. |
||
299 | View Code Duplication | if (!this.mark && !this.stat) { |
|
300 | for (var i = 0; i < entries.length; i ++) { |
||
301 | var e = entries[i] |
||
302 | if (abs === '/') |
||
303 | e = abs + e |
||
304 | else |
||
305 | e = abs + '/' + e |
||
306 | this.cache[e] = true |
||
307 | } |
||
308 | } |
||
309 | |||
310 | this.cache[abs] = entries |
||
311 | |||
312 | // mark and cache dir-ness |
||
313 | return entries |
||
314 | } |
||
315 | |||
316 | GlobSync.prototype._readdirError = function (f, er) { |
||
317 | // handle errors, and cache the information |
||
318 | switch (er.code) { |
||
319 | case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 |
||
320 | case 'ENOTDIR': // totally normal. means it *does* exist. |
||
321 | var abs = this._makeAbs(f) |
||
322 | this.cache[abs] = 'FILE' |
||
323 | if (abs === this.cwdAbs) { |
||
324 | var error = new Error(er.code + ' invalid cwd ' + this.cwd) |
||
325 | error.path = this.cwd |
||
326 | error.code = er.code |
||
327 | throw error |
||
328 | } |
||
329 | break |
||
330 | |||
331 | case 'ENOENT': // not terribly unusual |
||
332 | case 'ELOOP': |
||
333 | case 'ENAMETOOLONG': |
||
334 | case 'UNKNOWN': |
||
335 | this.cache[this._makeAbs(f)] = false |
||
336 | break |
||
337 | |||
338 | default: // some unusual error. Treat as failure. |
||
339 | this.cache[this._makeAbs(f)] = false |
||
340 | if (this.strict) |
||
341 | throw er |
||
342 | if (!this.silent) |
||
343 | console.error('glob error', er) |
||
344 | break |
||
345 | } |
||
346 | } |
||
347 | |||
348 | GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { |
||
349 | |||
350 | var entries = this._readdir(abs, inGlobStar) |
||
351 | |||
352 | // no entries means not a dir, so it can never have matches |
||
353 | // foo.txt/** doesn't match foo.txt |
||
354 | if (!entries) |
||
355 | return |
||
356 | |||
357 | // test without the globstar, and with every child both below |
||
358 | // and replacing the globstar. |
||
359 | var remainWithoutGlobStar = remain.slice(1) |
||
360 | var gspref = prefix ? [ prefix ] : [] |
||
361 | var noGlobStar = gspref.concat(remainWithoutGlobStar) |
||
362 | |||
363 | // the noGlobStar pattern exits the inGlobStar state |
||
364 | this._process(noGlobStar, index, false) |
||
365 | |||
366 | var len = entries.length |
||
367 | var isSym = this.symlinks[abs] |
||
368 | |||
369 | // If it's a symlink, and we're in a globstar, then stop |
||
370 | if (isSym && inGlobStar) |
||
371 | return |
||
372 | |||
373 | for (var i = 0; i < len; i++) { |
||
374 | var e = entries[i] |
||
375 | if (e.charAt(0) === '.' && !this.dot) |
||
376 | continue |
||
377 | |||
378 | // these two cases enter the inGlobStar state |
||
379 | var instead = gspref.concat(entries[i], remainWithoutGlobStar) |
||
380 | this._process(instead, index, true) |
||
381 | |||
382 | var below = gspref.concat(entries[i], remain) |
||
383 | this._process(below, index, true) |
||
384 | } |
||
385 | } |
||
386 | |||
387 | GlobSync.prototype._processSimple = function (prefix, index) { |
||
388 | // XXX review this. Shouldn't it be doing the mounting etc |
||
389 | // before doing stat? kinda weird? |
||
390 | var exists = this._stat(prefix) |
||
391 | |||
392 | if (!this.matches[index]) |
||
393 | this.matches[index] = Object.create(null) |
||
394 | |||
395 | // If it doesn't exist, then just mark the lack of results |
||
396 | if (!exists) |
||
397 | return |
||
398 | |||
399 | if (prefix && isAbsolute(prefix) && !this.nomount) { |
||
400 | var trail = /[\/\\]$/.test(prefix) |
||
401 | if (prefix.charAt(0) === '/') { |
||
402 | prefix = path.join(this.root, prefix) |
||
403 | } else { |
||
404 | prefix = path.resolve(this.root, prefix) |
||
405 | if (trail) |
||
406 | prefix += '/' |
||
407 | } |
||
408 | } |
||
409 | |||
410 | if (process.platform === 'win32') |
||
411 | prefix = prefix.replace(/\\/g, '/') |
||
412 | |||
413 | // Mark this as a match |
||
414 | this._emitMatch(index, prefix) |
||
415 | } |
||
416 | |||
417 | // Returns either 'DIR', 'FILE', or false |
||
418 | GlobSync.prototype._stat = function (f) { |
||
419 | var abs = this._makeAbs(f) |
||
420 | var needDir = f.slice(-1) === '/' |
||
421 | |||
422 | if (f.length > this.maxLength) |
||
423 | return false |
||
424 | |||
425 | if (!this.stat && ownProp(this.cache, abs)) { |
||
426 | var c = this.cache[abs] |
||
427 | |||
428 | if (Array.isArray(c)) |
||
429 | c = 'DIR' |
||
430 | |||
431 | // It exists, but maybe not how we need it |
||
432 | if (!needDir || c === 'DIR') |
||
433 | return c |
||
434 | |||
435 | if (needDir && c === 'FILE') |
||
436 | return false |
||
437 | |||
438 | // otherwise we have to stat, because maybe c=true |
||
439 | // if we know it exists, but not what it is. |
||
440 | } |
||
441 | |||
442 | var exists |
||
443 | var stat = this.statCache[abs] |
||
444 | if (!stat) { |
||
445 | var lstat |
||
446 | try { |
||
447 | lstat = fs.lstatSync(abs) |
||
448 | } catch (er) { |
||
449 | if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { |
||
450 | this.statCache[abs] = false |
||
451 | return false |
||
452 | } |
||
453 | } |
||
454 | |||
455 | if (lstat && lstat.isSymbolicLink()) { |
||
456 | try { |
||
457 | stat = fs.statSync(abs) |
||
458 | } catch (er) { |
||
459 | stat = lstat |
||
460 | } |
||
461 | } else { |
||
462 | stat = lstat |
||
463 | } |
||
464 | } |
||
465 | |||
466 | this.statCache[abs] = stat |
||
467 | |||
468 | var c = true |
||
469 | if (stat) |
||
470 | c = stat.isDirectory() ? 'DIR' : 'FILE' |
||
471 | |||
472 | this.cache[abs] = this.cache[abs] || c |
||
473 | |||
474 | if (needDir && c === 'FILE') |
||
475 | return false |
||
476 | |||
477 | return c |
||
478 | } |
||
479 | |||
480 | GlobSync.prototype._mark = function (p) { |
||
481 | return common.mark(this, p) |
||
482 | } |
||
483 | |||
484 | GlobSync.prototype._makeAbs = function (f) { |
||
485 | return common.makeAbs(this, f) |
||
486 | } |
||
487 |
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.
Consider:
If you or someone else later decides to put another statement in, only the first statement will be executed.
In this case the statement
b = 42
will always be executed, while the logging statement will be executed conditionally.ensures that the proper code will be executed conditionally no matter how many statements are added or removed.