source: imaps-frontend/node_modules/ignore/index.js@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 16.0 KB
Line 
1// A simple implementation of make-array
2function makeArray (subject) {
3 return Array.isArray(subject)
4 ? subject
5 : [subject]
6}
7
8const EMPTY = ''
9const SPACE = ' '
10const ESCAPE = '\\'
11const REGEX_TEST_BLANK_LINE = /^\s+$/
12const REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/
13const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/
14const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/
15const REGEX_SPLITALL_CRLF = /\r?\n/g
16// /foo,
17// ./foo,
18// ../foo,
19// .
20// ..
21const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/
22
23const SLASH = '/'
24
25// Do not use ternary expression here, since "istanbul ignore next" is buggy
26let TMP_KEY_IGNORE = 'node-ignore'
27/* istanbul ignore else */
28if (typeof Symbol !== 'undefined') {
29 TMP_KEY_IGNORE = Symbol.for('node-ignore')
30}
31const KEY_IGNORE = TMP_KEY_IGNORE
32
33const define = (object, key, value) =>
34 Object.defineProperty(object, key, {value})
35
36const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g
37
38const RETURN_FALSE = () => false
39
40// Sanitize the range of a regular expression
41// The cases are complicated, see test cases for details
42const sanitizeRange = range => range.replace(
43 REGEX_REGEXP_RANGE,
44 (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0)
45 ? match
46 // Invalid range (out of order) which is ok for gitignore rules but
47 // fatal for JavaScript regular expression, so eliminate it.
48 : EMPTY
49)
50
51// See fixtures #59
52const cleanRangeBackSlash = slashes => {
53 const {length} = slashes
54 return slashes.slice(0, length - length % 2)
55}
56
57// > If the pattern ends with a slash,
58// > it is removed for the purpose of the following description,
59// > but it would only find a match with a directory.
60// > In other words, foo/ will match a directory foo and paths underneath it,
61// > but will not match a regular file or a symbolic link foo
62// > (this is consistent with the way how pathspec works in general in Git).
63// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`'
64// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call
65// you could use option `mark: true` with `glob`
66
67// '`foo/`' should not continue with the '`..`'
68const REPLACERS = [
69
70 [
71 // remove BOM
72 // TODO:
73 // Other similar zero-width characters?
74 /^\uFEFF/,
75 () => EMPTY
76 ],
77
78 // > Trailing spaces are ignored unless they are quoted with backslash ("\")
79 [
80 // (a\ ) -> (a )
81 // (a ) -> (a)
82 // (a ) -> (a)
83 // (a \ ) -> (a )
84 /((?:\\\\)*?)(\\?\s+)$/,
85 (_, m1, m2) => m1 + (
86 m2.indexOf('\\') === 0
87 ? SPACE
88 : EMPTY
89 )
90 ],
91
92 // replace (\ ) with ' '
93 // (\ ) -> ' '
94 // (\\ ) -> '\\ '
95 // (\\\ ) -> '\\ '
96 [
97 /(\\+?)\s/g,
98 (_, m1) => {
99 const {length} = m1
100 return m1.slice(0, length - length % 2) + SPACE
101 }
102 ],
103
104 // Escape metacharacters
105 // which is written down by users but means special for regular expressions.
106
107 // > There are 12 characters with special meanings:
108 // > - the backslash \,
109 // > - the caret ^,
110 // > - the dollar sign $,
111 // > - the period or dot .,
112 // > - the vertical bar or pipe symbol |,
113 // > - the question mark ?,
114 // > - the asterisk or star *,
115 // > - the plus sign +,
116 // > - the opening parenthesis (,
117 // > - the closing parenthesis ),
118 // > - and the opening square bracket [,
119 // > - the opening curly brace {,
120 // > These special characters are often called "metacharacters".
121 [
122 /[\\$.|*+(){^]/g,
123 match => `\\${match}`
124 ],
125
126 [
127 // > a question mark (?) matches a single character
128 /(?!\\)\?/g,
129 () => '[^/]'
130 ],
131
132 // leading slash
133 [
134
135 // > A leading slash matches the beginning of the pathname.
136 // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
137 // A leading slash matches the beginning of the pathname
138 /^\//,
139 () => '^'
140 ],
141
142 // replace special metacharacter slash after the leading slash
143 [
144 /\//g,
145 () => '\\/'
146 ],
147
148 [
149 // > A leading "**" followed by a slash means match in all directories.
150 // > For example, "**/foo" matches file or directory "foo" anywhere,
151 // > the same as pattern "foo".
152 // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
153 // > under directory "foo".
154 // Notice that the '*'s have been replaced as '\\*'
155 /^\^*\\\*\\\*\\\//,
156
157 // '**/foo' <-> 'foo'
158 () => '^(?:.*\\/)?'
159 ],
160
161 // starting
162 [
163 // there will be no leading '/'
164 // (which has been replaced by section "leading slash")
165 // If starts with '**', adding a '^' to the regular expression also works
166 /^(?=[^^])/,
167 function startingReplacer () {
168 // If has a slash `/` at the beginning or middle
169 return !/\/(?!$)/.test(this)
170 // > Prior to 2.22.1
171 // > If the pattern does not contain a slash /,
172 // > Git treats it as a shell glob pattern
173 // Actually, if there is only a trailing slash,
174 // git also treats it as a shell glob pattern
175
176 // After 2.22.1 (compatible but clearer)
177 // > If there is a separator at the beginning or middle (or both)
178 // > of the pattern, then the pattern is relative to the directory
179 // > level of the particular .gitignore file itself.
180 // > Otherwise the pattern may also match at any level below
181 // > the .gitignore level.
182 ? '(?:^|\\/)'
183
184 // > Otherwise, Git treats the pattern as a shell glob suitable for
185 // > consumption by fnmatch(3)
186 : '^'
187 }
188 ],
189
190 // two globstars
191 [
192 // Use lookahead assertions so that we could match more than one `'/**'`
193 /\\\/\\\*\\\*(?=\\\/|$)/g,
194
195 // Zero, one or several directories
196 // should not use '*', or it will be replaced by the next replacer
197
198 // Check if it is not the last `'/**'`
199 (_, index, str) => index + 6 < str.length
200
201 // case: /**/
202 // > A slash followed by two consecutive asterisks then a slash matches
203 // > zero or more directories.
204 // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on.
205 // '/**/'
206 ? '(?:\\/[^\\/]+)*'
207
208 // case: /**
209 // > A trailing `"/**"` matches everything inside.
210
211 // #21: everything inside but it should not include the current folder
212 : '\\/.+'
213 ],
214
215 // normal intermediate wildcards
216 [
217 // Never replace escaped '*'
218 // ignore rule '\*' will match the path '*'
219
220 // 'abc.*/' -> go
221 // 'abc.*' -> skip this rule,
222 // coz trailing single wildcard will be handed by [trailing wildcard]
223 /(^|[^\\]+)(\\\*)+(?=.+)/g,
224
225 // '*.js' matches '.js'
226 // '*.js' doesn't match 'abc'
227 (_, p1, p2) => {
228 // 1.
229 // > An asterisk "*" matches anything except a slash.
230 // 2.
231 // > Other consecutive asterisks are considered regular asterisks
232 // > and will match according to the previous rules.
233 const unescaped = p2.replace(/\\\*/g, '[^\\/]*')
234 return p1 + unescaped
235 }
236 ],
237
238 [
239 // unescape, revert step 3 except for back slash
240 // For example, if a user escape a '\\*',
241 // after step 3, the result will be '\\\\\\*'
242 /\\\\\\(?=[$.|*+(){^])/g,
243 () => ESCAPE
244 ],
245
246 [
247 // '\\\\' -> '\\'
248 /\\\\/g,
249 () => ESCAPE
250 ],
251
252 [
253 // > The range notation, e.g. [a-zA-Z],
254 // > can be used to match one of the characters in a range.
255
256 // `\` is escaped by step 3
257 /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
258 (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE
259 // '\\[bar]' -> '\\\\[bar\\]'
260 ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`
261 : close === ']'
262 ? endEscape.length % 2 === 0
263 // A normal case, and it is a range notation
264 // '[bar]'
265 // '[bar\\\\]'
266 ? `[${sanitizeRange(range)}${endEscape}]`
267 // Invalid range notaton
268 // '[bar\\]' -> '[bar\\\\]'
269 : '[]'
270 : '[]'
271 ],
272
273 // ending
274 [
275 // 'js' will not match 'js.'
276 // 'ab' will not match 'abc'
277 /(?:[^*])$/,
278
279 // WTF!
280 // https://git-scm.com/docs/gitignore
281 // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
282 // which re-fixes #24, #38
283
284 // > If there is a separator at the end of the pattern then the pattern
285 // > will only match directories, otherwise the pattern can match both
286 // > files and directories.
287
288 // 'js*' will not match 'a.js'
289 // 'js/' will not match 'a.js'
290 // 'js' will match 'a.js' and 'a.js/'
291 match => /\/$/.test(match)
292 // foo/ will not match 'foo'
293 ? `${match}$`
294 // foo matches 'foo' and 'foo/'
295 : `${match}(?=$|\\/$)`
296 ],
297
298 // trailing wildcard
299 [
300 /(\^|\\\/)?\\\*$/,
301 (_, p1) => {
302 const prefix = p1
303 // '\^':
304 // '/*' does not match EMPTY
305 // '/*' does not match everything
306
307 // '\\\/':
308 // 'abc/*' does not match 'abc/'
309 ? `${p1}[^/]+`
310
311 // 'a*' matches 'a'
312 // 'a*' matches 'aa'
313 : '[^/]*'
314
315 return `${prefix}(?=$|\\/$)`
316 }
317 ],
318]
319
320// A simple cache, because an ignore rule only has only one certain meaning
321const regexCache = Object.create(null)
322
323// @param {pattern}
324const makeRegex = (pattern, ignoreCase) => {
325 let source = regexCache[pattern]
326
327 if (!source) {
328 source = REPLACERS.reduce(
329 (prev, [matcher, replacer]) =>
330 prev.replace(matcher, replacer.bind(pattern)),
331 pattern
332 )
333 regexCache[pattern] = source
334 }
335
336 return ignoreCase
337 ? new RegExp(source, 'i')
338 : new RegExp(source)
339}
340
341const isString = subject => typeof subject === 'string'
342
343// > A blank line matches no files, so it can serve as a separator for readability.
344const checkPattern = pattern => pattern
345 && isString(pattern)
346 && !REGEX_TEST_BLANK_LINE.test(pattern)
347 && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern)
348
349 // > A line starting with # serves as a comment.
350 && pattern.indexOf('#') !== 0
351
352const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF)
353
354class IgnoreRule {
355 constructor (
356 origin,
357 pattern,
358 negative,
359 regex
360 ) {
361 this.origin = origin
362 this.pattern = pattern
363 this.negative = negative
364 this.regex = regex
365 }
366}
367
368const createRule = (pattern, ignoreCase) => {
369 const origin = pattern
370 let negative = false
371
372 // > An optional prefix "!" which negates the pattern;
373 if (pattern.indexOf('!') === 0) {
374 negative = true
375 pattern = pattern.substr(1)
376 }
377
378 pattern = pattern
379 // > Put a backslash ("\") in front of the first "!" for patterns that
380 // > begin with a literal "!", for example, `"\!important!.txt"`.
381 .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!')
382 // > Put a backslash ("\") in front of the first hash for patterns that
383 // > begin with a hash.
384 .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#')
385
386 const regex = makeRegex(pattern, ignoreCase)
387
388 return new IgnoreRule(
389 origin,
390 pattern,
391 negative,
392 regex
393 )
394}
395
396const throwError = (message, Ctor) => {
397 throw new Ctor(message)
398}
399
400const checkPath = (path, originalPath, doThrow) => {
401 if (!isString(path)) {
402 return doThrow(
403 `path must be a string, but got \`${originalPath}\``,
404 TypeError
405 )
406 }
407
408 // We don't know if we should ignore EMPTY, so throw
409 if (!path) {
410 return doThrow(`path must not be empty`, TypeError)
411 }
412
413 // Check if it is a relative path
414 if (checkPath.isNotRelative(path)) {
415 const r = '`path.relative()`d'
416 return doThrow(
417 `path should be a ${r} string, but got "${originalPath}"`,
418 RangeError
419 )
420 }
421
422 return true
423}
424
425const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path)
426
427checkPath.isNotRelative = isNotRelative
428checkPath.convert = p => p
429
430class Ignore {
431 constructor ({
432 ignorecase = true,
433 ignoreCase = ignorecase,
434 allowRelativePaths = false
435 } = {}) {
436 define(this, KEY_IGNORE, true)
437
438 this._rules = []
439 this._ignoreCase = ignoreCase
440 this._allowRelativePaths = allowRelativePaths
441 this._initCache()
442 }
443
444 _initCache () {
445 this._ignoreCache = Object.create(null)
446 this._testCache = Object.create(null)
447 }
448
449 _addPattern (pattern) {
450 // #32
451 if (pattern && pattern[KEY_IGNORE]) {
452 this._rules = this._rules.concat(pattern._rules)
453 this._added = true
454 return
455 }
456
457 if (checkPattern(pattern)) {
458 const rule = createRule(pattern, this._ignoreCase)
459 this._added = true
460 this._rules.push(rule)
461 }
462 }
463
464 // @param {Array<string> | string | Ignore} pattern
465 add (pattern) {
466 this._added = false
467
468 makeArray(
469 isString(pattern)
470 ? splitPattern(pattern)
471 : pattern
472 ).forEach(this._addPattern, this)
473
474 // Some rules have just added to the ignore,
475 // making the behavior changed.
476 if (this._added) {
477 this._initCache()
478 }
479
480 return this
481 }
482
483 // legacy
484 addPattern (pattern) {
485 return this.add(pattern)
486 }
487
488 // | ignored : unignored
489 // negative | 0:0 | 0:1 | 1:0 | 1:1
490 // -------- | ------- | ------- | ------- | --------
491 // 0 | TEST | TEST | SKIP | X
492 // 1 | TESTIF | SKIP | TEST | X
493
494 // - SKIP: always skip
495 // - TEST: always test
496 // - TESTIF: only test if checkUnignored
497 // - X: that never happen
498
499 // @param {boolean} whether should check if the path is unignored,
500 // setting `checkUnignored` to `false` could reduce additional
501 // path matching.
502
503 // @returns {TestResult} true if a file is ignored
504 _testOne (path, checkUnignored) {
505 let ignored = false
506 let unignored = false
507
508 this._rules.forEach(rule => {
509 const {negative} = rule
510 if (
511 unignored === negative && ignored !== unignored
512 || negative && !ignored && !unignored && !checkUnignored
513 ) {
514 return
515 }
516
517 const matched = rule.regex.test(path)
518
519 if (matched) {
520 ignored = !negative
521 unignored = negative
522 }
523 })
524
525 return {
526 ignored,
527 unignored
528 }
529 }
530
531 // @returns {TestResult}
532 _test (originalPath, cache, checkUnignored, slices) {
533 const path = originalPath
534 // Supports nullable path
535 && checkPath.convert(originalPath)
536
537 checkPath(
538 path,
539 originalPath,
540 this._allowRelativePaths
541 ? RETURN_FALSE
542 : throwError
543 )
544
545 return this._t(path, cache, checkUnignored, slices)
546 }
547
548 _t (path, cache, checkUnignored, slices) {
549 if (path in cache) {
550 return cache[path]
551 }
552
553 if (!slices) {
554 // path/to/a.js
555 // ['path', 'to', 'a.js']
556 slices = path.split(SLASH)
557 }
558
559 slices.pop()
560
561 // If the path has no parent directory, just test it
562 if (!slices.length) {
563 return cache[path] = this._testOne(path, checkUnignored)
564 }
565
566 const parent = this._t(
567 slices.join(SLASH) + SLASH,
568 cache,
569 checkUnignored,
570 slices
571 )
572
573 // If the path contains a parent directory, check the parent first
574 return cache[path] = parent.ignored
575 // > It is not possible to re-include a file if a parent directory of
576 // > that file is excluded.
577 ? parent
578 : this._testOne(path, checkUnignored)
579 }
580
581 ignores (path) {
582 return this._test(path, this._ignoreCache, false).ignored
583 }
584
585 createFilter () {
586 return path => !this.ignores(path)
587 }
588
589 filter (paths) {
590 return makeArray(paths).filter(this.createFilter())
591 }
592
593 // @returns {TestResult}
594 test (path) {
595 return this._test(path, this._testCache, true)
596 }
597}
598
599const factory = options => new Ignore(options)
600
601const isPathValid = path =>
602 checkPath(path && checkPath.convert(path), path, RETURN_FALSE)
603
604factory.isPathValid = isPathValid
605
606// Fixes typescript
607factory.default = factory
608
609module.exports = factory
610
611// Windows
612// --------------------------------------------------------------
613/* istanbul ignore if */
614if (
615 // Detect `process` so that it can run in browsers.
616 typeof process !== 'undefined'
617 && (
618 process.env && process.env.IGNORE_TEST_WIN32
619 || process.platform === 'win32'
620 )
621) {
622 /* eslint no-control-regex: "off" */
623 const makePosix = str => /^\\\\\?\\/.test(str)
624 || /["<>|\u0000-\u001F]+/u.test(str)
625 ? str
626 : str.replace(/\\/g, '/')
627
628 checkPath.convert = makePosix
629
630 // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/'
631 // 'd:\\foo'
632 const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i
633 checkPath.isNotRelative = path =>
634 REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path)
635 || isNotRelative(path)
636}
Note: See TracBrowser for help on using the repository browser.