Passed
Push — main ( 10dbfc...61a674 )
by LCS
05:39 queued 03:09
created

node_modules/glob/sync.js   F

Complexity

Total Complexity 96
Complexity/F 5.65

Size

Lines of Code 486
Function Count 17

Duplication

Duplicated Lines 36
Ratio 7.41 %

Importance

Changes 0
Metric Value
eloc 293
dl 36
loc 486
rs 2
c 0
b 0
f 0
wmc 96
mnd 79
bc 79
fnc 17
bpm 4.647
cpm 5.647
noi 54

How to fix   Duplicated Code    Complexity   

Duplicated Code

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:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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 rp = require('fs.realpath')
5
var minimatch = require('minimatch')
6
var Minimatch = minimatch.Minimatch
7
var Glob = require('./glob.js').Glob
8
var util = require('util')
9
var path = require('path')
10
var assert = require('assert')
11
var isAbsolute = require('path-is-absolute')
12
var common = require('./common.js')
13
var setopts = common.setopts
14
var ownProp = common.ownProp
15
var childrenIgnored = common.childrenIgnored
16
var isIgnored = common.isIgnored
17
18
function globSync (pattern, options) {
19
  if (typeof options === 'function' || arguments.length === 3)
20
    throw new TypeError('callback provided to sync glob\n'+
21
                        'See: https://github.com/isaacs/node-glob/issues/167')
22
23
  return new GlobSync(pattern, options).found
24
}
25
26
function GlobSync (pattern, options) {
27
  if (!pattern)
28
    throw new Error('must provide pattern')
29
30
  if (typeof options === 'function' || arguments.length === 3)
31
    throw new TypeError('callback provided to sync glob\n'+
32
                        'See: https://github.com/isaacs/node-glob/issues/167')
33
34
  if (!(this instanceof GlobSync))
35
    return new GlobSync(pattern, options)
36
37
  setopts(this, pattern, options)
38
39
  if (this.noprocess)
40
    return this
41
42
  var n = this.minimatch.set.length
43
  this.matches = new Array(n)
44
  for (var i = 0; i < n; i ++) {
45
    this._process(this.minimatch.set[i], i, false)
46
  }
47
  this._finish()
48
}
49
50
GlobSync.prototype._finish = function () {
51
  assert.ok(this instanceof GlobSync)
52
  if (this.realpath) {
53
    var self = this
54
    this.matches.forEach(function (matchset, index) {
55
      var set = self.matches[index] = Object.create(null)
56
      for (var p in matchset) {
57
        try {
58
          p = self._makeAbs(p)
59
          var real = rp.realpathSync(p, self.realpathCache)
60
          set[real] = true
61
        } catch (er) {
62
          if (er.syscall === 'stat')
63
            set[self._makeAbs(p)] = true
64
          else
65
            throw er
66
        }
67
      }
68
    })
69
  }
70
  common.finish(this)
71
}
72
73
74
GlobSync.prototype._process = function (pattern, index, inGlobStar) {
75
  assert.ok(this instanceof GlobSync)
76
77
  // Get the first [n] parts of pattern that are all strings.
78
  var n = 0
79
  while (typeof pattern[n] === 'string') {
80
    n ++
81
  }
82
  // now n is the index of the first one that is *not* a string.
83
84
  // See if there's anything else
85
  var prefix
86
  switch (n) {
87
    // if not, then this is rather simple
88
    case pattern.length:
89
      this._processSimple(pattern.join('/'), index)
90
      return
91
92
    case 0:
93
      // pattern *starts* with some non-trivial item.
94
      // going to readdir(cwd), but not include the prefix in matches.
95
      prefix = null
96
      break
97
98
    default:
99
      // pattern has some string bits in the front.
100
      // whatever it starts with, whether that's 'absolute' like /foo/bar,
101
      // or 'relative' like '../baz'
102
      prefix = pattern.slice(0, n).join('/')
103
      break
104
  }
105
106
  var remain = pattern.slice(n)
107
108
  // get the list of entries.
109
  var read
110
  if (prefix === null)
111
    read = '.'
112
  else if (isAbsolute(prefix) ||
113
      isAbsolute(pattern.map(function (p) {
114
        return typeof p === 'string' ? p : '[*]'
115
      }).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
  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 = this.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, this.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
  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 = this.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 = this.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