source: trip-planner-front/node_modules/ignore/index.js@ 188ee53

Last change on this file since 188ee53 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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