• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

micromatch / picomatch / 23459036476

23 Mar 2026 08:37PM UTC coverage: 92.256% (-0.1%) from 92.355%
23459036476

push

github

danez
Publish 3.0.2

871 of 963 branches covered (90.45%)

Branch coverage included in aggregate %.

1047 of 1116 relevant lines covered (93.82%)

3470.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.35
/lib/parse.js
1
'use strict';
2

3
const constants = require('./constants');
1✔
4
const utils = require('./utils');
1✔
5

6
/**
7
 * Constants
8
 */
9

10
const {
11
  MAX_LENGTH,
12
  POSIX_REGEX_SOURCE,
13
  REGEX_NON_SPECIAL_CHARS,
14
  REGEX_SPECIAL_CHARS_BACKREF,
15
  REPLACEMENTS
16
} = constants;
1✔
17

18
/**
19
 * Helpers
20
 */
21

22
const expandRange = (args, options) => {
1✔
23
  if (typeof options.expandRange === 'function') {
23✔
24
    return options.expandRange(...args, options);
20✔
25
  }
26

27
  args.sort();
3✔
28
  const value = `[${args.join('-')}]`;
3✔
29

30
  try {
3✔
31
    /* eslint-disable-next-line no-new */
32
    new RegExp(value);
3✔
33
  } catch (ex) {
34
    return args.map(v => utils.escapeRegex(v)).join('..');
×
35
  }
36

37
  return value;
3✔
38
};
39

40
/**
41
 * Create the message for a syntax error
42
 */
43

44
const syntaxError = (type, char) => {
1✔
45
  return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
6✔
46
};
47

48
const splitTopLevel = input => {
1✔
49
  const parts = [];
3,546✔
50
  let bracket = 0;
3,546✔
51
  let paren = 0;
3,546✔
52
  let quote = 0;
3,546✔
53
  let value = '';
3,546✔
54
  let escaped = false;
3,546✔
55

56
  for (const ch of input) {
3,546✔
57
    if (escaped === true) {
79,807✔
58
      value += ch;
32,800✔
59
      escaped = false;
32,800✔
60
      continue;
32,800✔
61
    }
62

63
    if (ch === '\\') {
47,007✔
64
      value += ch;
32,800✔
65
      escaped = true;
32,800✔
66
      continue;
32,800✔
67
    }
68

69
    if (ch === '"') {
14,207!
70
      quote = quote === 1 ? 0 : 1;
×
71
      value += ch;
×
72
      continue;
×
73
    }
74

75
    if (quote === 0) {
14,207!
76
      if (ch === '[') {
14,207✔
77
        bracket++;
239✔
78
      } else if (ch === ']' && bracket > 0) {
13,968✔
79
        bracket--;
220✔
80
      } else if (bracket === 0) {
13,748✔
81
        if (ch === '(') {
13,051✔
82
          paren++;
1,210✔
83
        } else if (ch === ')' && paren > 0) {
11,841✔
84
          paren--;
1,210✔
85
        } else if (ch === '|' && paren === 0) {
10,631✔
86
          parts.push(value);
938✔
87
          value = '';
938✔
88
          continue;
938✔
89
        }
90
      }
91
    }
92

93
    value += ch;
13,269✔
94
  }
95

96
  parts.push(value);
3,546✔
97
  return parts;
3,546✔
98
};
99

100
const isPlainBranch = branch => {
1✔
101
  let escaped = false;
1,783✔
102

103
  for (const ch of branch) {
1,783✔
104
    if (escaped === true) {
2,901✔
105
      escaped = false;
25✔
106
      continue;
25✔
107
    }
108

109
    if (ch === '\\') {
2,876✔
110
      escaped = true;
25✔
111
      continue;
25✔
112
    }
113

114
    if (/[?*+@!()[\]{}]/.test(ch)) {
2,851✔
115
      return false;
342✔
116
    }
117
  }
118

119
  return true;
1,441✔
120
};
121

122
const normalizeSimpleBranch = branch => {
1✔
123
  let value = branch.trim();
1,783✔
124
  let changed = true;
1,783✔
125

126
  while (changed === true) {
1,783✔
127
    changed = false;
1,783✔
128

129
    if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
1,783!
130
      value = value.slice(2, -1);
×
131
      changed = true;
×
132
    }
133
  }
134

135
  if (!isPlainBranch(value)) {
1,783✔
136
    return;
342✔
137
  }
138

139
  return value.replace(/\\(.)/g, '$1');
1,441✔
140
};
141

142
const hasRepeatedCharPrefixOverlap = branches => {
1✔
143
  const values = branches.map(normalizeSimpleBranch).filter(Boolean);
720✔
144

145
  for (let i = 0; i < values.length; i++) {
720✔
146
    for (let j = i + 1; j < values.length; j++) {
1,340✔
147
      const a = values[i];
860✔
148
      const b = values[j];
860✔
149
      const char = a[0];
860✔
150

151
      if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) {
860✔
152
        continue;
846✔
153
      }
154

155
      if (a === b || a.startsWith(b) || b.startsWith(a)) {
14!
156
        return true;
14✔
157
      }
158
    }
159
  }
160

161
  return false;
706✔
162
};
163

164
const parseRepeatedExtglob = (pattern, requireEnd = true) => {
1✔
165
  if ((pattern[0] !== '+' && pattern[0] !== '*') || pattern[1] !== '(') {
8,603✔
166
    return;
8,389✔
167
  }
168

169
  let bracket = 0;
214✔
170
  let paren = 0;
214✔
171
  let quote = 0;
214✔
172
  let escaped = false;
214✔
173

174
  for (let i = 1; i < pattern.length; i++) {
214✔
175
    const ch = pattern[i];
1,262✔
176

177
    if (escaped === true) {
1,262!
178
      escaped = false;
×
179
      continue;
×
180
    }
181

182
    if (ch === '\\') {
1,262!
183
      escaped = true;
×
184
      continue;
×
185
    }
186

187
    if (ch === '"') {
1,262!
188
      quote = quote === 1 ? 0 : 1;
×
189
      continue;
×
190
    }
191

192
    if (quote === 1) {
1,262!
193
      continue;
×
194
    }
195

196
    if (ch === '[') {
1,262!
197
      bracket++;
×
198
      continue;
×
199
    }
200

201
    if (ch === ']' && bracket > 0) {
1,262!
202
      bracket--;
×
203
      continue;
×
204
    }
205

206
    if (bracket > 0) {
1,262!
207
      continue;
×
208
    }
209

210
    if (ch === '(') {
1,262✔
211
      paren++;
312✔
212
      continue;
312✔
213
    }
214

215
    if (ch === ')') {
950✔
216
      paren--;
312✔
217

218
      if (paren === 0) {
312✔
219
        if (requireEnd === true && i !== pattern.length - 1) {
214✔
220
          return;
49✔
221
        }
222

223
        return {
165✔
224
          type: pattern[0],
225
          body: pattern.slice(2, i),
226
          end: i
227
        };
228
      }
229
    }
230
  }
231
};
232

233
const getStarExtglobSequenceOutput = pattern => {
1✔
234
  let index = 0;
4,300✔
235
  const chars = [];
4,300✔
236

237
  while (index < pattern.length) {
4,300✔
238
    const match = parseRepeatedExtglob(pattern.slice(index), false);
4,336✔
239

240
    if (!match || match.type !== '*') {
4,336✔
241
      return;
4,193✔
242
    }
243

244
    const branches = splitTopLevel(match.body).map(branch => branch.trim());
151✔
245
    if (branches.length !== 1) {
143✔
246
      return;
8✔
247
    }
248

249
    const branch = normalizeSimpleBranch(branches[0]);
135✔
250
    if (!branch || branch.length !== 1) {
135✔
251
      return;
49✔
252
    }
253

254
    chars.push(branch);
86✔
255
    index += match.end + 1;
86✔
256
  }
257

258
  if (chars.length < 1) {
50✔
259
    return;
2✔
260
  }
261

262
  const source = chars.length === 1
48✔
263
    ? utils.escapeRegex(chars[0])
264
    : `[${chars.map(ch => utils.escapeRegex(ch)).join('')}]`;
76✔
265

266
  return `${source}*`;
48✔
267
};
268

269
const repeatedExtglobRecursion = pattern => {
1✔
270
  let depth = 0;
4,252✔
271
  let value = pattern.trim();
4,252✔
272
  let match = parseRepeatedExtglob(value);
4,252✔
273

274
  while (match) {
4,252✔
275
    depth++;
15✔
276
    value = match.body.trim();
15✔
277
    match = parseRepeatedExtglob(value);
15✔
278
  }
279

280
  return depth;
4,252✔
281
};
282

283
const analyzeRepeatedExtglob = (body, options) => {
1✔
284
  if (options.maxExtglobRecursion === false) {
3,406✔
285
    return { risky: false };
3✔
286
  }
287

288
  const max =
289
    typeof options.maxExtglobRecursion === 'number'
3,403✔
290
      ? options.maxExtglobRecursion
291
      : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
292

293
  const branches = splitTopLevel(body).map(branch => branch.trim());
4,333✔
294

295
  if (branches.length > 1) {
3,403✔
296
    if (
722✔
297
      branches.some(branch => branch === '') ||
1,652✔
298
      branches.some(branch => /^[*?]+$/.test(branch)) ||
1,650✔
299
      hasRepeatedCharPrefixOverlap(branches)
300
    ) {
301
      return { risky: true };
16✔
302
    }
303
  }
304

305
  for (const branch of branches) {
3,387✔
306
    const safeOutput = getStarExtglobSequenceOutput(branch);
4,300✔
307
    if (safeOutput) {
4,300✔
308
      return { risky: true, safeOutput };
48✔
309
    }
310

311
    if (repeatedExtglobRecursion(branch) > max) {
4,252✔
312
      return { risky: true };
11✔
313
    }
314
  }
315

316
  return { risky: false };
3,328✔
317
};
318

319
/**
320
 * Parse the given input string.
321
 * @param {String} input
322
 * @param {Object} options
323
 * @return {Object}
324
 */
325

326
const parse = (input, options) => {
1✔
327
  if (typeof input !== 'string') {
8,562!
328
    throw new TypeError('Expected a string');
×
329
  }
330

331
  input = REPLACEMENTS[input] || input;
8,562✔
332

333
  const opts = { ...options };
8,562✔
334
  const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
8,562✔
335

336
  let len = input.length;
8,562✔
337
  if (len > max) {
8,562✔
338
    throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
2✔
339
  }
340

341
  const bos = { type: 'bos', value: '', output: opts.prepend || '' };
8,560✔
342
  const tokens = [bos];
8,560✔
343

344
  const capture = opts.capture ? '' : '?:';
8,560✔
345

346
  // create constants based on platform, for windows or posix
347
  const PLATFORM_CHARS = constants.globChars(opts.windows);
8,560✔
348
  const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
8,560✔
349

350
  const {
351
    DOT_LITERAL,
352
    PLUS_LITERAL,
353
    SLASH_LITERAL,
354
    ONE_CHAR,
355
    DOTS_SLASH,
356
    NO_DOT,
357
    NO_DOT_SLASH,
358
    NO_DOTS_SLASH,
359
    QMARK,
360
    QMARK_NO_DOT,
361
    STAR,
362
    START_ANCHOR
363
  } = PLATFORM_CHARS;
8,560✔
364

365
  const globstar = opts => {
8,560✔
366
    return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
2,982✔
367
  };
368

369
  const nodot = opts.dot ? '' : NO_DOT;
8,560✔
370
  const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
8,560✔
371
  let star = opts.bash === true ? globstar(opts) : STAR;
8,560✔
372

373
  if (opts.capture) {
8,560✔
374
    star = `(${star})`;
2✔
375
  }
376

377
  // minimatch options support
378
  if (typeof opts.noext === 'boolean') {
8,560✔
379
    opts.noextglob = opts.noext;
4✔
380
  }
381

382
  const state = {
8,560✔
383
    input,
384
    index: -1,
385
    start: 0,
386
    dot: opts.dot === true,
387
    consumed: '',
388
    output: '',
389
    prefix: '',
390
    backtrack: false,
391
    negated: false,
392
    brackets: 0,
393
    braces: 0,
394
    parens: 0,
395
    quotes: 0,
396
    globstar: false,
397
    tokens
398
  };
399

400
  input = utils.removePrefix(input, state);
8,560✔
401
  len = input.length;
8,560✔
402

403
  const extglobs = [];
8,560✔
404
  const braces = [];
8,560✔
405
  const stack = [];
8,560✔
406
  let prev = bos;
8,560✔
407
  let value;
408

409
  /**
410
   * Tokenizing helpers
411
   */
412

413
  const eos = () => state.index === len - 1;
65,464✔
414
  const peek = state.peek = (n = 1) => input[state.index + n];
15,644✔
415
  const advance = state.advance = () => input[++state.index] || '';
60,147!
416
  const remaining = () => input.slice(state.index + 1);
27,973✔
417
  const consume = (value = '', num = 0) => {
8,560!
418
    state.consumed += value;
60,292✔
419
    state.index += num;
60,292✔
420
  };
421

422
  const append = token => {
8,560✔
423
    state.output += token.output != null ? token.output : token.value;
57,756✔
424
    consume(token.value);
57,756✔
425
  };
426

427
  const negate = () => {
8,560✔
428
    let count = 1;
327✔
429

430
    while (peek() === '!' && (peek(2) !== '(' || peek(3) === '?')) {
327✔
431
      advance();
70✔
432
      state.start++;
70✔
433
      count++;
70✔
434
    }
435

436
    if (count % 2 === 0) {
327✔
437
      return false;
10✔
438
    }
439

440
    state.negated = true;
317✔
441
    state.start++;
317✔
442
    return true;
317✔
443
  };
444

445
  const increment = type => {
8,560✔
446
    state[type]++;
5,858✔
447
    stack.push(type);
5,858✔
448
  };
449

450
  const decrement = type => {
8,560✔
451
    state[type]--;
5,858✔
452
    stack.pop();
5,858✔
453
  };
454

455
  /**
456
   * Push tokens onto the tokens array. This helper speeds up
457
   * tokenizing by 1) helping us avoid backtracking as much as possible,
458
   * and 2) helping us avoid creating extra tokens when consecutive
459
   * characters are plain text. This improves performance and simplifies
460
   * lookbehinds.
461
   */
462

463
  const push = tok => {
8,560✔
464
    if (prev.type === 'globstar') {
51,204✔
465
      const isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace');
1,565✔
466
      const isExtglob = tok.extglob === true || (extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'));
1,565!
467

468
      if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) {
1,565✔
469
        state.output = state.output.slice(0, -prev.output.length);
299✔
470
        prev.type = 'star';
299✔
471
        prev.value = '*';
299✔
472
        prev.output = star;
299✔
473
        state.output += prev.output;
299✔
474
      }
475
    }
476

477
    if (extglobs.length && tok.type !== 'paren') {
51,204✔
478
      extglobs[extglobs.length - 1].inner += tok.value;
7,278✔
479
    }
480

481
    if (tok.value || tok.output) append(tok);
51,204!
482
    if (prev && prev.type === 'text' && tok.type === 'text') {
51,204✔
483
      prev.value += tok.value;
3,633✔
484
      prev.output = (prev.output || '') + tok.value;
3,633✔
485
      return;
3,633✔
486
    }
487

488
    tok.prev = prev;
47,571✔
489
    tokens.push(tok);
47,571✔
490
    prev = tok;
47,571✔
491
  };
492

493
  const extglobOpen = (type, value) => {
8,560✔
494
    const token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' };
3,407✔
495

496
    token.prev = prev;
3,407✔
497
    token.parens = state.parens;
3,407✔
498
    token.output = state.output;
3,407✔
499
    token.startIndex = state.index;
3,407✔
500
    token.tokensIndex = tokens.length;
3,407✔
501
    const output = (opts.capture ? '(' : '') + token.open;
3,407✔
502

503
    increment('parens');
3,407✔
504
    push({ type, value, output: state.output ? '' : ONE_CHAR });
3,407✔
505
    push({ type: 'paren', extglob: true, value: advance(), output });
3,407✔
506
    extglobs.push(token);
3,407✔
507
  };
508

509
  const extglobClose = token => {
8,560✔
510
    const literal = input.slice(token.startIndex, state.index + 1);
3,406✔
511
    const body = input.slice(token.startIndex + 2, state.index);
3,406✔
512
    const analysis = analyzeRepeatedExtglob(body, opts);
3,406✔
513

514
    if ((token.type === 'plus' || token.type === 'star') && analysis.risky) {
3,406✔
515
      const safeOutput = analysis.safeOutput
58✔
516
        ? (token.output ? '' : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput)
80✔
517
        : undefined;
518
      const open = tokens[token.tokensIndex];
58✔
519

520
      open.type = 'text';
58✔
521
      open.value = literal;
58✔
522
      open.output = safeOutput || utils.escapeRegex(literal);
58✔
523

524
      for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
58✔
525
        tokens[i].value = '';
401✔
526
        tokens[i].output = '';
401✔
527
        delete tokens[i].suffix;
401✔
528
      }
529

530
      state.output = token.output + open.output;
58✔
531
      state.backtrack = true;
58✔
532

533
      push({ type: 'paren', extglob: true, value, output: '' });
58✔
534
      decrement('parens');
58✔
535
      return;
58✔
536
    }
537

538
    let output = token.close + (opts.capture ? ')' : '');
3,348✔
539
    let rest;
540

541
    if (token.type === 'negate') {
3,348✔
542
      let extglobStar = star;
1,568✔
543

544
      if (token.inner && token.inner.length > 1 && token.inner.includes('/')) {
1,568✔
545
        extglobStar = globstar(opts);
69✔
546
      }
547

548
      if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) {
1,568✔
549
        output = token.close = `)$))${extglobStar}`;
1,068✔
550
      }
551

552
      if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
1,568✔
553
        // Any non-magical string (`.ts`) or even nested expression (`.{ts,tsx}`) can follow after the closing parenthesis.
554
        // In this case, we need to parse the string and use it in the output of the original pattern.
555
        // Suitable patterns: `/!(*.d).ts`, `/!(*.d).{ts,tsx}`, `**/!(*-dbg).@(js)`.
556
        //
557
        // Disabling the `fastpaths` option due to a problem with parsing strings as `.ts` in the pattern like `**/!(*.d).ts`.
558
        const expression = parse(rest, { ...options, fastpaths: false }).output;
28✔
559

560
        output = token.close = `)${expression})${extglobStar})`;
28✔
561
      }
562

563
      if (token.prev.type === 'bos') {
1,568✔
564
        state.negatedExtglob = true;
707✔
565
      }
566
    }
567

568
    push({ type: 'paren', extglob: true, value, output });
3,348✔
569
    decrement('parens');
3,348✔
570
  };
571

572
  /**
573
   * Fast paths
574
   */
575

576
  if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
8,560✔
577
    let backslashes = false;
784✔
578

579
    let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
784✔
580
      if (first === '\\') {
1,426✔
581
        backslashes = true;
57✔
582
        return m;
57✔
583
      }
584

585
      if (first === '?') {
1,369✔
586
        if (esc) {
303✔
587
          return esc + first + (rest ? QMARK.repeat(rest.length) : '');
6✔
588
        }
589
        if (index === 0) {
297✔
590
          return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : '');
117✔
591
        }
592
        return QMARK.repeat(chars.length);
180✔
593
      }
594

595
      if (first === '.') {
1,066✔
596
        return DOT_LITERAL.repeat(chars.length);
141✔
597
      }
598

599
      if (first === '*') {
925✔
600
        if (esc) {
661✔
601
          return esc + first + (rest ? star : '');
112✔
602
        }
603
        return star;
549✔
604
      }
605
      return esc ? m : `\\${m}`;
264✔
606
    });
607

608
    if (backslashes === true) {
784✔
609
      if (opts.unescape === true) {
47✔
610
        output = output.replace(/\\/g, '');
1✔
611
      } else {
612
        output = output.replace(/\\+/g, m => {
46✔
613
          return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : '');
54!
614
        });
615
      }
616
    }
617

618
    if (output === input && opts.contains === true) {
784!
619
      state.output = input;
×
620
      return state;
×
621
    }
622

623
    state.output = utils.wrapOutput(output, state, options);
784✔
624
    return state;
784✔
625
  }
626

627
  /**
628
   * Tokenize input until we reach end-of-string
629
   */
630

631
  while (!eos()) {
7,776✔
632
    value = advance();
54,531✔
633

634
    if (value === '\u0000') {
54,531!
635
      continue;
×
636
    }
637

638
    /**
639
     * Escaped characters
640
     */
641

642
    if (value === '\\') {
54,531✔
643
      const next = peek();
578✔
644

645
      if (next === '/' && opts.bash !== true) {
578✔
646
        continue;
6✔
647
      }
648

649
      if (next === '.' || next === ';') {
572✔
650
        continue;
24✔
651
      }
652

653
      if (!next) {
548!
654
        value += '\\';
×
655
        push({ type: 'text', value });
×
656
        continue;
×
657
      }
658

659
      // collapse slashes to reduce potential for exploits
660
      const match = /^\\+/.exec(remaining());
548✔
661
      let slashes = 0;
548✔
662

663
      if (match && match[0].length > 2) {
548✔
664
        slashes = match[0].length;
3✔
665
        state.index += slashes;
3✔
666
        if (slashes % 2 !== 0) {
3!
667
          value += '\\';
3✔
668
        }
669
      }
670

671
      if (opts.unescape === true) {
548✔
672
        value = advance();
28✔
673
      } else {
674
        value += advance();
520✔
675
      }
676

677
      if (state.brackets === 0) {
548✔
678
        push({ type: 'text', value });
475✔
679
        continue;
475✔
680
      }
681
    }
682

683
    /**
684
     * If we're inside a regex character class, continue
685
     * until we reach the closing bracket.
686
     */
687

688
    if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) {
54,026✔
689
      if (opts.posix !== false && value === ':') {
5,521✔
690
        const inner = prev.value.slice(1);
739✔
691
        if (inner.includes('[')) {
739✔
692
          prev.posix = true;
709✔
693

694
          if (inner.includes(':')) {
709✔
695
            const idx = prev.value.lastIndexOf('[');
366✔
696
            const pre = prev.value.slice(0, idx);
366✔
697
            const rest = prev.value.slice(idx + 2);
366✔
698
            const posix = POSIX_REGEX_SOURCE[rest];
366✔
699
            if (posix) {
366✔
700
              prev.value = pre + posix;
350✔
701
              state.backtrack = true;
350✔
702
              advance();
350✔
703

704
              if (!bos.output && tokens.indexOf(prev) === 1) {
350✔
705
                bos.output = ONE_CHAR;
155✔
706
              }
707
              continue;
350✔
708
            }
709
          }
710
        }
711
      }
712

713
      if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) {
5,171✔
714
        value = `\\${value}`;
19✔
715
      }
716

717
      if (value === ']' && (prev.value === '[' || prev.value === '[^')) {
5,171!
718
        value = `\\${value}`;
6✔
719
      }
720

721
      if (opts.posix === true && value === '!' && prev.value === '[') {
5,171✔
722
        value = '^';
10✔
723
      }
724

725
      prev.value += value;
5,171✔
726
      append({ value });
5,171✔
727
      continue;
5,171✔
728
    }
729

730
    /**
731
     * If we're inside a quoted string, continue
732
     * until we reach the closing double quote.
733
     */
734

735
    if (state.quotes === 1 && value !== '"') {
48,505✔
736
      value = utils.escapeRegex(value);
228✔
737
      prev.value += value;
228✔
738
      append({ value });
228✔
739
      continue;
228✔
740
    }
741

742
    /**
743
     * Double quotes
744
     */
745

746
    if (value === '"') {
48,277✔
747
      state.quotes = state.quotes === 1 ? 0 : 1;
206✔
748
      if (opts.keepQuotes === true) {
206✔
749
        push({ type: 'text', value });
12✔
750
      }
751
      continue;
206✔
752
    }
753

754
    /**
755
     * Parentheses
756
     */
757

758
    if (value === '(') {
48,071✔
759
      increment('parens');
1,090✔
760
      push({ type: 'paren', value });
1,090✔
761
      continue;
1,090✔
762
    }
763

764
    if (value === ')') {
46,981✔
765
      if (state.parens === 0 && opts.strictBrackets === true) {
4,466✔
766
        throw new SyntaxError(syntaxError('opening', '('));
2✔
767
      }
768

769
      const extglob = extglobs[extglobs.length - 1];
4,464✔
770
      if (extglob && state.parens === extglob.parens + 1) {
4,464✔
771
        extglobClose(extglobs.pop());
3,406✔
772
        continue;
3,406✔
773
      }
774

775
      push({ type: 'paren', value, output: state.parens ? ')' : '\\)' });
1,058✔
776
      decrement('parens');
1,058✔
777
      continue;
1,058✔
778
    }
779

780
    /**
781
     * Square brackets
782
     */
783

784
    if (value === '[') {
42,515✔
785
      if (opts.nobracket === true || !remaining().includes(']')) {
1,174✔
786
        if (opts.nobracket !== true && opts.strictBrackets === true) {
18✔
787
          throw new SyntaxError(syntaxError('closing', ']'));
1✔
788
        }
789

790
        value = `\\${value}`;
17✔
791
      } else {
792
        increment('brackets');
1,156✔
793
      }
794

795
      push({ type: 'bracket', value });
1,173✔
796
      continue;
1,173✔
797
    }
798

799
    if (value === ']') {
41,341✔
800
      if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) {
1,165✔
801
        push({ type: 'text', value, output: `\\${value}` });
3✔
802
        continue;
3✔
803
      }
804

805
      if (state.brackets === 0) {
1,162✔
806
        if (opts.strictBrackets === true) {
9✔
807
          throw new SyntaxError(syntaxError('opening', '['));
1✔
808
        }
809

810
        push({ type: 'text', value, output: `\\${value}` });
8✔
811
        continue;
8✔
812
      }
813

814
      decrement('brackets');
1,153✔
815

816
      const prevValue = prev.value.slice(1);
1,153✔
817
      if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) {
1,153✔
818
        value = `/${value}`;
156✔
819
      }
820

821
      prev.value += value;
1,153✔
822
      append({ value });
1,153✔
823

824
      // when literal brackets are explicitly disabled
825
      // assume we should match with a regex character class
826
      if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) {
1,153✔
827
        continue;
964✔
828
      }
829

830
      const escaped = utils.escapeRegex(prev.value);
189✔
831
      state.output = state.output.slice(0, -prev.value.length);
189✔
832

833
      // when literal brackets are explicitly enabled
834
      // assume we should escape the brackets to match literal characters
835
      if (opts.literalBrackets === true) {
189!
836
        state.output += escaped;
×
837
        prev.value = escaped;
×
838
        continue;
×
839
      }
840

841
      // when the user specifies nothing, try to match both
842
      prev.value = `(${capture}${escaped}|${prev.value})`;
189✔
843
      state.output += prev.value;
189✔
844
      continue;
189✔
845
    }
846

847
    /**
848
     * Braces
849
     */
850

851
    if (value === '{' && opts.nobrace !== true) {
40,176✔
852
      increment('braces');
205✔
853

854
      const open = {
205✔
855
        type: 'brace',
856
        value,
857
        output: '(',
858
        outputIndex: state.output.length,
859
        tokensIndex: state.tokens.length
860
      };
861

862
      braces.push(open);
205✔
863
      push(open);
205✔
864
      continue;
205✔
865
    }
866

867
    if (value === '}') {
39,971✔
868
      const brace = braces[braces.length - 1];
216✔
869

870
      if (opts.nobrace === true || !brace) {
216✔
871
        push({ type: 'text', value, output: value });
11✔
872
        continue;
11✔
873
      }
874

875
      let output = ')';
205✔
876

877
      if (brace.dots === true) {
205✔
878
        const arr = tokens.slice();
23✔
879
        const range = [];
23✔
880

881
        for (let i = arr.length - 1; i >= 0; i--) {
23✔
882
          tokens.pop();
92✔
883
          if (arr[i].type === 'brace') {
92✔
884
            break;
23✔
885
          }
886
          if (arr[i].type !== 'dots') {
69✔
887
            range.unshift(arr[i].value);
46✔
888
          }
889
        }
890

891
        output = expandRange(range, opts);
23✔
892
        state.backtrack = true;
23✔
893
      }
894

895
      if (brace.comma !== true && brace.dots !== true) {
205✔
896
        const out = state.output.slice(0, brace.outputIndex);
3✔
897
        const toks = state.tokens.slice(brace.tokensIndex);
3✔
898
        brace.value = brace.output = '\\{';
3✔
899
        value = output = '\\}';
3✔
900
        state.output = out;
3✔
901
        for (const t of toks) {
3✔
902
          state.output += (t.output || t.value);
8✔
903
        }
904
      }
905

906
      push({ type: 'brace', value, output });
205✔
907
      decrement('braces');
205✔
908
      braces.pop();
205✔
909
      continue;
205✔
910
    }
911

912
    /**
913
     * Pipes
914
     */
915

916
    if (value === '|') {
39,755✔
917
      if (extglobs.length > 0) {
1,528✔
918
        extglobs[extglobs.length - 1].conditions++;
959✔
919
      }
920
      push({ type: 'text', value });
1,528✔
921
      continue;
1,528✔
922
    }
923

924
    /**
925
     * Commas
926
     */
927

928
    if (value === ',') {
38,227✔
929
      let output = value;
274✔
930

931
      const brace = braces[braces.length - 1];
274✔
932
      if (brace && stack[stack.length - 1] === 'braces') {
274✔
933
        brace.comma = true;
231✔
934
        output = '|';
231✔
935
      }
936

937
      push({ type: 'comma', value, output });
274✔
938
      continue;
274✔
939
    }
940

941
    /**
942
     * Slashes
943
     */
944

945
    if (value === '/') {
37,953✔
946
      // if the beginning of the glob is "./", advance the start
947
      // to the current index, and don't add the "./" characters
948
      // to the state. This greatly simplifies lookbehinds when
949
      // checking for BOS characters like "!" and "." (not "./")
950
      if (prev.type === 'dot' && state.index === state.start + 1) {
6,606!
951
        state.start = state.index + 1;
×
952
        state.consumed = '';
×
953
        state.output = '';
×
954
        tokens.pop();
×
955
        prev = bos; // reset "prev" to the first token
×
956
        continue;
×
957
      }
958

959
      push({ type: 'slash', value, output: SLASH_LITERAL });
6,606✔
960
      continue;
6,606✔
961
    }
962

963
    /**
964
     * Dots
965
     */
966

967
    if (value === '.') {
31,347✔
968
      if (state.braces > 0 && prev.type === 'dot') {
2,512✔
969
        if (prev.value === '.') prev.output = DOT_LITERAL;
23!
970
        const brace = braces[braces.length - 1];
23✔
971
        prev.type = 'dots';
23✔
972
        prev.output += value;
23✔
973
        prev.value += value;
23✔
974
        brace.dots = true;
23✔
975
        continue;
23✔
976
      }
977

978
      if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') {
2,489✔
979
        push({ type: 'text', value, output: DOT_LITERAL });
1,493✔
980
        continue;
1,493✔
981
      }
982

983
      push({ type: 'dot', value, output: DOT_LITERAL });
996✔
984
      continue;
996✔
985
    }
986

987
    /**
988
     * Question marks
989
     */
990

991
    if (value === '?') {
28,835✔
992
      const isGroup = prev && prev.value === '(';
453✔
993
      if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
453✔
994
        extglobOpen('qmark', value);
130✔
995
        continue;
130✔
996
      }
997

998
      if (prev && prev.type === 'paren') {
323✔
999
        const next = peek();
28✔
1000
        let output = value;
28✔
1001

1002
        if (next === '<' && !utils.supportsLookbehinds()) {
28✔
1003
          throw new Error('Node.js v10 or higher is required for regex lookbehinds');
1✔
1004
        }
1005

1006
        if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) {
27✔
1007
          output = `\\${value}`;
2✔
1008
        }
1009

1010
        push({ type: 'text', value, output });
27✔
1011
        continue;
27✔
1012
      }
1013

1014
      if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) {
295✔
1015
        push({ type: 'qmark', value, output: QMARK_NO_DOT });
129✔
1016
        continue;
129✔
1017
      }
1018

1019
      push({ type: 'qmark', value, output: QMARK });
166✔
1020
      continue;
166✔
1021
    }
1022

1023
    /**
1024
     * Exclamation
1025
     */
1026

1027
    if (value === '!') {
28,382✔
1028
      if (opts.noextglob !== true && peek() === '(') {
1,919✔
1029
        if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) {
1,574✔
1030
          extglobOpen('negate', value);
1,569✔
1031
          continue;
1,569✔
1032
        }
1033
      }
1034

1035
      if (opts.nonegate !== true && state.index === 0) {
350✔
1036
        negate();
327✔
1037
        continue;
327✔
1038
      }
1039
    }
1040

1041
    /**
1042
     * Plus
1043
     */
1044

1045
    if (value === '+') {
26,486✔
1046
      if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
749✔
1047
        extglobOpen('plus', value);
492✔
1048
        continue;
492✔
1049
      }
1050

1051
      if ((prev && prev.value === '(') || opts.regex === false) {
257✔
1052
        push({ type: 'plus', value, output: PLUS_LITERAL });
2✔
1053
        continue;
2✔
1054
      }
1055

1056
      if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) {
255✔
1057
        push({ type: 'plus', value });
217✔
1058
        continue;
217✔
1059
      }
1060

1061
      push({ type: 'plus', value: PLUS_LITERAL });
38✔
1062
      continue;
38✔
1063
    }
1064

1065
    /**
1066
     * Plain text
1067
     */
1068

1069
    if (value === '@') {
25,737✔
1070
      if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
567✔
1071
        push({ type: 'at', extglob: true, value, output: '' });
562✔
1072
        continue;
562✔
1073
      }
1074

1075
      push({ type: 'text', value });
5✔
1076
      continue;
5✔
1077
    }
1078

1079
    /**
1080
     * Plain text
1081
     */
1082

1083
    if (value !== '*') {
25,170✔
1084
      if (value === '$' || value === '^') {
11,694✔
1085
        value = `\\${value}`;
75✔
1086
      }
1087

1088
      const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
11,694✔
1089
      if (match) {
11,694✔
1090
        value += match[0];
3,037✔
1091
        state.index += match[0].length;
3,037✔
1092
      }
1093

1094
      push({ type: 'text', value });
11,694✔
1095
      continue;
11,694✔
1096
    }
1097

1098
    /**
1099
     * Stars
1100
     */
1101

1102
    if (prev && (prev.type === 'globstar' || prev.star === true)) {
13,476✔
1103
      prev.type = 'star';
97✔
1104
      prev.star = true;
97✔
1105
      prev.value += value;
97✔
1106
      prev.output = star;
97✔
1107
      state.backtrack = true;
97✔
1108
      state.globstar = true;
97✔
1109
      consume(value);
97✔
1110
      continue;
97✔
1111
    }
1112

1113
    let rest = remaining();
13,379✔
1114
    if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
13,379✔
1115
      extglobOpen('star', value);
1,216✔
1116
      continue;
1,216✔
1117
    }
1118

1119
    if (prev.type === 'star') {
12,163✔
1120
      if (opts.noglobstar === true) {
2,516✔
1121
        consume(value);
3✔
1122
        continue;
3✔
1123
      }
1124

1125
      const prior = prev.prev;
2,513✔
1126
      const before = prior.prev;
2,513✔
1127
      const isStart = prior.type === 'slash' || prior.type === 'bos';
2,513✔
1128
      const afterStar = before && (before.type === 'star' || before.type === 'globstar');
2,513✔
1129

1130
      if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) {
2,513✔
1131
        push({ type: 'star', value, output: '' });
3✔
1132
        continue;
3✔
1133
      }
1134

1135
      const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace');
2,510✔
1136
      const isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren');
2,510✔
1137
      if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) {
2,510✔
1138
        push({ type: 'star', value, output: '' });
346✔
1139
        continue;
346✔
1140
      }
1141

1142
      // strip consecutive `/**/`
1143
      while (rest.slice(0, 3) === '/**') {
2,164✔
1144
        const after = input[state.index + 4];
488✔
1145
        if (after && after !== '/') {
488✔
1146
          break;
216✔
1147
        }
1148
        rest = rest.slice(3);
272✔
1149
        consume('/**', 3);
272✔
1150
      }
1151

1152
      if (prior.type === 'bos' && eos()) {
2,164✔
1153
        prev.type = 'globstar';
24✔
1154
        prev.value += value;
24✔
1155
        prev.output = globstar(opts);
24✔
1156
        state.output = prev.output;
24✔
1157
        state.globstar = true;
24✔
1158
        consume(value);
24✔
1159
        continue;
24✔
1160
      }
1161

1162
      if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) {
2,140✔
1163
        state.output = state.output.slice(0, -(prior.output + prev.output).length);
371✔
1164
        prior.output = `(?:${prior.output}`;
371✔
1165

1166
        prev.type = 'globstar';
371✔
1167
        prev.output = globstar(opts) + (opts.strictSlashes ? ')' : '|$)');
371✔
1168
        prev.value += value;
371✔
1169
        state.globstar = true;
371✔
1170
        state.output += prior.output + prev.output;
371✔
1171
        consume(value);
371✔
1172
        continue;
371✔
1173
      }
1174

1175
      if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') {
1,769✔
1176
        const end = rest[1] !== void 0 ? '|$' : '';
495✔
1177

1178
        state.output = state.output.slice(0, -(prior.output + prev.output).length);
495✔
1179
        prior.output = `(?:${prior.output}`;
495✔
1180

1181
        prev.type = 'globstar';
495✔
1182
        prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
495✔
1183
        prev.value += value;
495✔
1184

1185
        state.output += prior.output + prev.output;
495✔
1186
        state.globstar = true;
495✔
1187

1188
        consume(value + advance());
495✔
1189

1190
        push({ type: 'slash', value: '/', output: '' });
495✔
1191
        continue;
495✔
1192
      }
1193

1194
      if (prior.type === 'bos' && rest[0] === '/') {
1,274✔
1195
        prev.type = 'globstar';
746✔
1196
        prev.value += value;
746✔
1197
        prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
746✔
1198
        state.output = prev.output;
746✔
1199
        state.globstar = true;
746✔
1200
        consume(value + advance());
746✔
1201
        push({ type: 'slash', value: '/', output: '' });
746✔
1202
        continue;
746✔
1203
      }
1204

1205
      // remove single star from output
1206
      state.output = state.output.slice(0, -prev.output.length);
528✔
1207

1208
      // reset previous token to globstar
1209
      prev.type = 'globstar';
528✔
1210
      prev.output = globstar(opts);
528✔
1211
      prev.value += value;
528✔
1212

1213
      // reset output with globstar
1214
      state.output += prev.output;
528✔
1215
      state.globstar = true;
528✔
1216
      consume(value);
528✔
1217
      continue;
528✔
1218
    }
1219

1220
    const token = { type: 'star', value, output: star };
9,647✔
1221

1222
    if (opts.bash === true) {
9,647✔
1223
      token.output = '.*?';
625✔
1224
      if (prev.type === 'bos' || prev.type === 'slash') {
625✔
1225
        token.output = nodot + token.output;
269✔
1226
      }
1227
      push(token);
625✔
1228
      continue;
625✔
1229
    }
1230

1231
    if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) {
9,022✔
1232
      token.output = value;
18✔
1233
      push(token);
18✔
1234
      continue;
18✔
1235
    }
1236

1237
    if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') {
9,004✔
1238
      if (prev.type === 'dot') {
7,186✔
1239
        state.output += NO_DOT_SLASH;
355✔
1240
        prev.output += NO_DOT_SLASH;
355✔
1241

1242
      } else if (opts.dot === true) {
6,831✔
1243
        state.output += NO_DOTS_SLASH;
1,072✔
1244
        prev.output += NO_DOTS_SLASH;
1,072✔
1245

1246
      } else {
1247
        state.output += nodot;
5,759✔
1248
        prev.output += nodot;
5,759✔
1249
      }
1250

1251
      if (peek() !== '*') {
7,186✔
1252
        state.output += ONE_CHAR;
4,940✔
1253
        prev.output += ONE_CHAR;
4,940✔
1254
      }
1255
    }
1256

1257
    push(token);
9,004✔
1258
  }
1259

1260
  while (state.brackets > 0) {
7,771✔
1261
    if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']'));
3!
1262
    state.output = utils.escapeLast(state.output, '[');
3✔
1263
    decrement('brackets');
3✔
1264
  }
1265

1266
  while (state.parens > 0) {
7,771✔
1267
    if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')'));
35✔
1268
    state.output = utils.escapeLast(state.output, '(');
33✔
1269
    decrement('parens');
33✔
1270
  }
1271

1272
  while (state.braces > 0) {
7,769✔
1273
    if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}'));
×
1274
    state.output = utils.escapeLast(state.output, '{');
×
1275
    decrement('braces');
×
1276
  }
1277

1278
  if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) {
7,769✔
1279
    push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` });
1,770✔
1280
  }
1281

1282
  // rebuild the output if we had to backtrack at any point
1283
  if (state.backtrack === true) {
7,769✔
1284
    state.output = '';
334✔
1285

1286
    for (const token of state.tokens) {
334✔
1287
      state.output += token.output != null ? token.output : token.value;
1,607✔
1288

1289
      if (token.suffix) {
1,607!
1290
        state.output += token.suffix;
×
1291
      }
1292
    }
1293
  }
1294

1295
  return state;
7,769✔
1296
};
1297

1298
/**
1299
 * Fast paths for creating regular expressions for common glob patterns.
1300
 * This can significantly speed up processing and has very little downside
1301
 * impact when none of the fast paths match.
1302
 */
1303

1304
parse.fastpaths = (input, options) => {
1✔
1305
  const opts = { ...options };
3,556✔
1306
  const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
3,556!
1307
  const len = input.length;
3,556✔
1308
  if (len > max) {
3,556✔
1309
    throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
1✔
1310
  }
1311

1312
  input = REPLACEMENTS[input] || input;
3,555✔
1313

1314
  // create constants based on platform, for windows or posix
1315
  const {
1316
    DOT_LITERAL,
1317
    SLASH_LITERAL,
1318
    ONE_CHAR,
1319
    DOTS_SLASH,
1320
    NO_DOT,
1321
    NO_DOTS,
1322
    NO_DOTS_SLASH,
1323
    STAR,
1324
    START_ANCHOR
1325
  } = constants.globChars(opts.windows);
3,555✔
1326

1327
  const nodot = opts.dot ? NO_DOTS : NO_DOT;
3,555✔
1328
  const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
3,555✔
1329
  const capture = opts.capture ? '' : '?:';
3,555✔
1330
  const state = { negated: false, prefix: '' };
3,555✔
1331
  let star = opts.bash === true ? '.*?' : STAR;
3,555✔
1332

1333
  if (opts.capture) {
3,555✔
1334
    star = `(${star})`;
1✔
1335
  }
1336

1337
  const globstar = opts => {
3,555✔
1338
    if (opts.noglobstar === true) return star;
302✔
1339
    return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
301✔
1340
  };
1341

1342
  const create = str => {
3,555✔
1343
    switch (str) {
3,792!
1344
      case '*':
1345
        return `${nodot}${ONE_CHAR}${star}`;
213✔
1346

1347
      case '.*':
1348
        return `${DOT_LITERAL}${ONE_CHAR}${star}`;
12✔
1349

1350
      case '*.*':
1351
        return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
10✔
1352

1353
      case '*/*':
1354
        return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
159✔
1355

1356
      case '**':
1357
        return nodot + globstar(opts);
200✔
1358

1359
      case '**/*':
1360
        return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
95✔
1361

1362
      case '**/*.*':
1363
        return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
×
1364

1365
      case '**/.*':
1366
        return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
7✔
1367

1368
      default: {
1369
        const match = /^(.*?)\.(\w+)$/.exec(str);
3,096✔
1370
        if (!match) return;
3,096✔
1371

1372
        const source = create(match[1]);
237✔
1373
        if (!source) return;
237✔
1374

1375
        return source + DOT_LITERAL + match[2];
125✔
1376
      }
1377
    }
1378
  };
1379

1380
  const output = utils.removePrefix(input, state);
3,555✔
1381
  let source = create(output);
3,555✔
1382

1383
  if (source && opts.strictSlashes !== true) {
3,555✔
1384
    source += `${SLASH_LITERAL}?`;
618✔
1385
  }
1386

1387
  return source;
3,555✔
1388
};
1389

1390
module.exports = parse;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc