You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1091 lines
27 KiB

2 years ago
  1. 'use strict';
  2. const constants = require('./constants');
  3. const utils = require('./utils');
  4. /**
  5. * Constants
  6. */
  7. const {
  8. MAX_LENGTH,
  9. POSIX_REGEX_SOURCE,
  10. REGEX_NON_SPECIAL_CHARS,
  11. REGEX_SPECIAL_CHARS_BACKREF,
  12. REPLACEMENTS
  13. } = constants;
  14. /**
  15. * Helpers
  16. */
  17. const expandRange = (args, options) => {
  18. if (typeof options.expandRange === 'function') {
  19. return options.expandRange(...args, options);
  20. }
  21. args.sort();
  22. const value = `[${args.join('-')}]`;
  23. try {
  24. /* eslint-disable-next-line no-new */
  25. new RegExp(value);
  26. } catch (ex) {
  27. return args.map(v => utils.escapeRegex(v)).join('..');
  28. }
  29. return value;
  30. };
  31. /**
  32. * Create the message for a syntax error
  33. */
  34. const syntaxError = (type, char) => {
  35. return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
  36. };
  37. /**
  38. * Parse the given input string.
  39. * @param {String} input
  40. * @param {Object} options
  41. * @return {Object}
  42. */
  43. const parse = (input, options) => {
  44. if (typeof input !== 'string') {
  45. throw new TypeError('Expected a string');
  46. }
  47. input = REPLACEMENTS[input] || input;
  48. const opts = { ...options };
  49. const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
  50. let len = input.length;
  51. if (len > max) {
  52. throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
  53. }
  54. const bos = { type: 'bos', value: '', output: opts.prepend || '' };
  55. const tokens = [bos];
  56. const capture = opts.capture ? '' : '?:';
  57. const win32 = utils.isWindows(options);
  58. // create constants based on platform, for windows or posix
  59. const PLATFORM_CHARS = constants.globChars(win32);
  60. const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
  61. const {
  62. DOT_LITERAL,
  63. PLUS_LITERAL,
  64. SLASH_LITERAL,
  65. ONE_CHAR,
  66. DOTS_SLASH,
  67. NO_DOT,
  68. NO_DOT_SLASH,
  69. NO_DOTS_SLASH,
  70. QMARK,
  71. QMARK_NO_DOT,
  72. STAR,
  73. START_ANCHOR
  74. } = PLATFORM_CHARS;
  75. const globstar = opts => {
  76. return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
  77. };
  78. const nodot = opts.dot ? '' : NO_DOT;
  79. const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
  80. let star = opts.bash === true ? globstar(opts) : STAR;
  81. if (opts.capture) {
  82. star = `(${star})`;
  83. }
  84. // minimatch options support
  85. if (typeof opts.noext === 'boolean') {
  86. opts.noextglob = opts.noext;
  87. }
  88. const state = {
  89. input,
  90. index: -1,
  91. start: 0,
  92. dot: opts.dot === true,
  93. consumed: '',
  94. output: '',
  95. prefix: '',
  96. backtrack: false,
  97. negated: false,
  98. brackets: 0,
  99. braces: 0,
  100. parens: 0,
  101. quotes: 0,
  102. globstar: false,
  103. tokens
  104. };
  105. input = utils.removePrefix(input, state);
  106. len = input.length;
  107. const extglobs = [];
  108. const braces = [];
  109. const stack = [];
  110. let prev = bos;
  111. let value;
  112. /**
  113. * Tokenizing helpers
  114. */
  115. const eos = () => state.index === len - 1;
  116. const peek = state.peek = (n = 1) => input[state.index + n];
  117. const advance = state.advance = () => input[++state.index] || '';
  118. const remaining = () => input.slice(state.index + 1);
  119. const consume = (value = '', num = 0) => {
  120. state.consumed += value;
  121. state.index += num;
  122. };
  123. const append = token => {
  124. state.output += token.output != null ? token.output : token.value;
  125. consume(token.value);
  126. };
  127. const negate = () => {
  128. let count = 1;
  129. while (peek() === '!' && (peek(2) !== '(' || peek(3) === '?')) {
  130. advance();
  131. state.start++;
  132. count++;
  133. }
  134. if (count % 2 === 0) {
  135. return false;
  136. }
  137. state.negated = true;
  138. state.start++;
  139. return true;
  140. };
  141. const increment = type => {
  142. state[type]++;
  143. stack.push(type);
  144. };
  145. const decrement = type => {
  146. state[type]--;
  147. stack.pop();
  148. };
  149. /**
  150. * Push tokens onto the tokens array. This helper speeds up
  151. * tokenizing by 1) helping us avoid backtracking as much as possible,
  152. * and 2) helping us avoid creating extra tokens when consecutive
  153. * characters are plain text. This improves performance and simplifies
  154. * lookbehinds.
  155. */
  156. const push = tok => {
  157. if (prev.type === 'globstar') {
  158. const isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace');
  159. const isExtglob = tok.extglob === true || (extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'));
  160. if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) {
  161. state.output = state.output.slice(0, -prev.output.length);
  162. prev.type = 'star';
  163. prev.value = '*';
  164. prev.output = star;
  165. state.output += prev.output;
  166. }
  167. }
  168. if (extglobs.length && tok.type !== 'paren') {
  169. extglobs[extglobs.length - 1].inner += tok.value;
  170. }
  171. if (tok.value || tok.output) append(tok);
  172. if (prev && prev.type === 'text' && tok.type === 'text') {
  173. prev.value += tok.value;
  174. prev.output = (prev.output || '') + tok.value;
  175. return;
  176. }
  177. tok.prev = prev;
  178. tokens.push(tok);
  179. prev = tok;
  180. };
  181. const extglobOpen = (type, value) => {
  182. const token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' };
  183. token.prev = prev;
  184. token.parens = state.parens;
  185. token.output = state.output;
  186. const output = (opts.capture ? '(' : '') + token.open;
  187. increment('parens');
  188. push({ type, value, output: state.output ? '' : ONE_CHAR });
  189. push({ type: 'paren', extglob: true, value: advance(), output });
  190. extglobs.push(token);
  191. };
  192. const extglobClose = token => {
  193. let output = token.close + (opts.capture ? ')' : '');
  194. let rest;
  195. if (token.type === 'negate') {
  196. let extglobStar = star;
  197. if (token.inner && token.inner.length > 1 && token.inner.includes('/')) {
  198. extglobStar = globstar(opts);
  199. }
  200. if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) {
  201. output = token.close = `)$))${extglobStar}`;
  202. }
  203. if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
  204. // Any non-magical string (`.ts`) or even nested expression (`.{ts,tsx}`) can follow after the closing parenthesis.
  205. // In this case, we need to parse the string and use it in the output of the original pattern.
  206. // Suitable patterns: `/!(*.d).ts`, `/!(*.d).{ts,tsx}`, `**/!(*-dbg).@(js)`.
  207. //
  208. // Disabling the `fastpaths` option due to a problem with parsing strings as `.ts` in the pattern like `**/!(*.d).ts`.
  209. const expression = parse(rest, { ...options, fastpaths: false }).output;
  210. output = token.close = `)${expression})${extglobStar})`;
  211. }
  212. if (token.prev.type === 'bos') {
  213. state.negatedExtglob = true;
  214. }
  215. }
  216. push({ type: 'paren', extglob: true, value, output });
  217. decrement('parens');
  218. };
  219. /**
  220. * Fast paths
  221. */
  222. if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
  223. let backslashes = false;
  224. let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
  225. if (first === '\\') {
  226. backslashes = true;
  227. return m;
  228. }
  229. if (first === '?') {
  230. if (esc) {
  231. return esc + first + (rest ? QMARK.repeat(rest.length) : '');
  232. }
  233. if (index === 0) {
  234. return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : '');
  235. }
  236. return QMARK.repeat(chars.length);
  237. }
  238. if (first === '.') {
  239. return DOT_LITERAL.repeat(chars.length);
  240. }
  241. if (first === '*') {
  242. if (esc) {
  243. return esc + first + (rest ? star : '');
  244. }
  245. return star;
  246. }
  247. return esc ? m : `\\${m}`;
  248. });
  249. if (backslashes === true) {
  250. if (opts.unescape === true) {
  251. output = output.replace(/\\/g, '');
  252. } else {
  253. output = output.replace(/\\+/g, m => {
  254. return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : '');
  255. });
  256. }
  257. }
  258. if (output === input && opts.contains === true) {
  259. state.output = input;
  260. return state;
  261. }
  262. state.output = utils.wrapOutput(output, state, options);
  263. return state;
  264. }
  265. /**
  266. * Tokenize input until we reach end-of-string
  267. */
  268. while (!eos()) {
  269. value = advance();
  270. if (value === '\u0000') {
  271. continue;
  272. }
  273. /**
  274. * Escaped characters
  275. */
  276. if (value === '\\') {
  277. const next = peek();
  278. if (next === '/' && opts.bash !== true) {
  279. continue;
  280. }
  281. if (next === '.' || next === ';') {
  282. continue;
  283. }
  284. if (!next) {
  285. value += '\\';
  286. push({ type: 'text', value });
  287. continue;
  288. }
  289. // collapse slashes to reduce potential for exploits
  290. const match = /^\\+/.exec(remaining());
  291. let slashes = 0;
  292. if (match && match[0].length > 2) {
  293. slashes = match[0].length;
  294. state.index += slashes;
  295. if (slashes % 2 !== 0) {
  296. value += '\\';
  297. }
  298. }
  299. if (opts.unescape === true) {
  300. value = advance();
  301. } else {
  302. value += advance();
  303. }
  304. if (state.brackets === 0) {
  305. push({ type: 'text', value });
  306. continue;
  307. }
  308. }
  309. /**
  310. * If we're inside a regex character class, continue
  311. * until we reach the closing bracket.
  312. */
  313. if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) {
  314. if (opts.posix !== false && value === ':') {
  315. const inner = prev.value.slice(1);
  316. if (inner.includes('[')) {
  317. prev.posix = true;
  318. if (inner.includes(':')) {
  319. const idx = prev.value.lastIndexOf('[');
  320. const pre = prev.value.slice(0, idx);
  321. const rest = prev.value.slice(idx + 2);
  322. const posix = POSIX_REGEX_SOURCE[rest];
  323. if (posix) {
  324. prev.value = pre + posix;
  325. state.backtrack = true;
  326. advance();
  327. if (!bos.output && tokens.indexOf(prev) === 1) {
  328. bos.output = ONE_CHAR;
  329. }
  330. continue;
  331. }
  332. }
  333. }
  334. }
  335. if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) {
  336. value = `\\${value}`;
  337. }
  338. if (value === ']' && (prev.value === '[' || prev.value === '[^')) {
  339. value = `\\${value}`;
  340. }
  341. if (opts.posix === true && value === '!' && prev.value === '[') {
  342. value = '^';
  343. }
  344. prev.value += value;
  345. append({ value });
  346. continue;
  347. }
  348. /**
  349. * If we're inside a quoted string, continue
  350. * until we reach the closing double quote.
  351. */
  352. if (state.quotes === 1 && value !== '"') {
  353. value = utils.escapeRegex(value);
  354. prev.value += value;
  355. append({ value });
  356. continue;
  357. }
  358. /**
  359. * Double quotes
  360. */
  361. if (value === '"') {
  362. state.quotes = state.quotes === 1 ? 0 : 1;
  363. if (opts.keepQuotes === true) {
  364. push({ type: 'text', value });
  365. }
  366. continue;
  367. }
  368. /**
  369. * Parentheses
  370. */
  371. if (value === '(') {
  372. increment('parens');
  373. push({ type: 'paren', value });
  374. continue;
  375. }
  376. if (value === ')') {
  377. if (state.parens === 0 && opts.strictBrackets === true) {
  378. throw new SyntaxError(syntaxError('opening', '('));
  379. }
  380. const extglob = extglobs[extglobs.length - 1];
  381. if (extglob && state.parens === extglob.parens + 1) {
  382. extglobClose(extglobs.pop());
  383. continue;
  384. }
  385. push({ type: 'paren', value, output: state.parens ? ')' : '\\)' });
  386. decrement('parens');
  387. continue;
  388. }
  389. /**
  390. * Square brackets
  391. */
  392. if (value === '[') {
  393. if (opts.nobracket === true || !remaining().includes(']')) {
  394. if (opts.nobracket !== true && opts.strictBrackets === true) {
  395. throw new SyntaxError(syntaxError('closing', ']'));
  396. }
  397. value = `\\${value}`;
  398. } else {
  399. increment('brackets');
  400. }
  401. push({ type: 'bracket', value });
  402. continue;
  403. }
  404. if (value === ']') {
  405. if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) {
  406. push({ type: 'text', value, output: `\\${value}` });
  407. continue;
  408. }
  409. if (state.brackets === 0) {
  410. if (opts.strictBrackets === true) {
  411. throw new SyntaxError(syntaxError('opening', '['));
  412. }
  413. push({ type: 'text', value, output: `\\${value}` });
  414. continue;
  415. }
  416. decrement('brackets');
  417. const prevValue = prev.value.slice(1);
  418. if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) {
  419. value = `/${value}`;
  420. }
  421. prev.value += value;
  422. append({ value });
  423. // when literal brackets are explicitly disabled
  424. // assume we should match with a regex character class
  425. if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) {
  426. continue;
  427. }
  428. const escaped = utils.escapeRegex(prev.value);
  429. state.output = state.output.slice(0, -prev.value.length);
  430. // when literal brackets are explicitly enabled
  431. // assume we should escape the brackets to match literal characters
  432. if (opts.literalBrackets === true) {
  433. state.output += escaped;
  434. prev.value = escaped;
  435. continue;
  436. }
  437. // when the user specifies nothing, try to match both
  438. prev.value = `(${capture}${escaped}|${prev.value})`;
  439. state.output += prev.value;
  440. continue;
  441. }
  442. /**
  443. * Braces
  444. */
  445. if (value === '{' && opts.nobrace !== true) {
  446. increment('braces');
  447. const open = {
  448. type: 'brace',
  449. value,
  450. output: '(',
  451. outputIndex: state.output.length,
  452. tokensIndex: state.tokens.length
  453. };
  454. braces.push(open);
  455. push(open);
  456. continue;
  457. }
  458. if (value === '}') {
  459. const brace = braces[braces.length - 1];
  460. if (opts.nobrace === true || !brace) {
  461. push({ type: 'text', value, output: value });
  462. continue;
  463. }
  464. let output = ')';
  465. if (brace.dots === true) {
  466. const arr = tokens.slice();
  467. const range = [];
  468. for (let i = arr.length - 1; i >= 0; i--) {
  469. tokens.pop();
  470. if (arr[i].type === 'brace') {
  471. break;
  472. }
  473. if (arr[i].type !== 'dots') {
  474. range.unshift(arr[i].value);
  475. }
  476. }
  477. output = expandRange(range, opts);
  478. state.backtrack = true;
  479. }
  480. if (brace.comma !== true && brace.dots !== true) {
  481. const out = state.output.slice(0, brace.outputIndex);
  482. const toks = state.tokens.slice(brace.tokensIndex);
  483. brace.value = brace.output = '\\{';
  484. value = output = '\\}';
  485. state.output = out;
  486. for (const t of toks) {
  487. state.output += (t.output || t.value);
  488. }
  489. }
  490. push({ type: 'brace', value, output });
  491. decrement('braces');
  492. braces.pop();
  493. continue;
  494. }
  495. /**
  496. * Pipes
  497. */
  498. if (value === '|') {
  499. if (extglobs.length > 0) {
  500. extglobs[extglobs.length - 1].conditions++;
  501. }
  502. push({ type: 'text', value });
  503. continue;
  504. }
  505. /**
  506. * Commas
  507. */
  508. if (value === ',') {
  509. let output = value;
  510. const brace = braces[braces.length - 1];
  511. if (brace && stack[stack.length - 1] === 'braces') {
  512. brace.comma = true;
  513. output = '|';
  514. }
  515. push({ type: 'comma', value, output });
  516. continue;
  517. }
  518. /**
  519. * Slashes
  520. */
  521. if (value === '/') {
  522. // if the beginning of the glob is "./", advance the start
  523. // to the current index, and don't add the "./" characters
  524. // to the state. This greatly simplifies lookbehinds when
  525. // checking for BOS characters like "!" and "." (not "./")
  526. if (prev.type === 'dot' && state.index === state.start + 1) {
  527. state.start = state.index + 1;
  528. state.consumed = '';
  529. state.output = '';
  530. tokens.pop();
  531. prev = bos; // reset "prev" to the first token
  532. continue;
  533. }
  534. push({ type: 'slash', value, output: SLASH_LITERAL });
  535. continue;
  536. }
  537. /**
  538. * Dots
  539. */
  540. if (value === '.') {
  541. if (state.braces > 0 && prev.type === 'dot') {
  542. if (prev.value === '.') prev.output = DOT_LITERAL;
  543. const brace = braces[braces.length - 1];
  544. prev.type = 'dots';
  545. prev.output += value;
  546. prev.value += value;
  547. brace.dots = true;
  548. continue;
  549. }
  550. if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') {
  551. push({ type: 'text', value, output: DOT_LITERAL });
  552. continue;
  553. }
  554. push({ type: 'dot', value, output: DOT_LITERAL });
  555. continue;
  556. }
  557. /**
  558. * Question marks
  559. */
  560. if (value === '?') {
  561. const isGroup = prev && prev.value === '(';
  562. if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  563. extglobOpen('qmark', value);
  564. continue;
  565. }
  566. if (prev && prev.type === 'paren') {
  567. const next = peek();
  568. let output = value;
  569. if (next === '<' && !utils.supportsLookbehinds()) {
  570. throw new Error('Node.js v10 or higher is required for regex lookbehinds');
  571. }
  572. if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) {
  573. output = `\\${value}`;
  574. }
  575. push({ type: 'text', value, output });
  576. continue;
  577. }
  578. if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) {
  579. push({ type: 'qmark', value, output: QMARK_NO_DOT });
  580. continue;
  581. }
  582. push({ type: 'qmark', value, output: QMARK });
  583. continue;
  584. }
  585. /**
  586. * Exclamation
  587. */
  588. if (value === '!') {
  589. if (opts.noextglob !== true && peek() === '(') {
  590. if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) {
  591. extglobOpen('negate', value);
  592. continue;
  593. }
  594. }
  595. if (opts.nonegate !== true && state.index === 0) {
  596. negate();
  597. continue;
  598. }
  599. }
  600. /**
  601. * Plus
  602. */
  603. if (value === '+') {
  604. if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  605. extglobOpen('plus', value);
  606. continue;
  607. }
  608. if ((prev && prev.value === '(') || opts.regex === false) {
  609. push({ type: 'plus', value, output: PLUS_LITERAL });
  610. continue;
  611. }
  612. if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) {
  613. push({ type: 'plus', value });
  614. continue;
  615. }
  616. push({ type: 'plus', value: PLUS_LITERAL });
  617. continue;
  618. }
  619. /**
  620. * Plain text
  621. */
  622. if (value === '@') {
  623. if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  624. push({ type: 'at', extglob: true, value, output: '' });
  625. continue;
  626. }
  627. push({ type: 'text', value });
  628. continue;
  629. }
  630. /**
  631. * Plain text
  632. */
  633. if (value !== '*') {
  634. if (value === '$' || value === '^') {
  635. value = `\\${value}`;
  636. }
  637. const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
  638. if (match) {
  639. value += match[0];
  640. state.index += match[0].length;
  641. }
  642. push({ type: 'text', value });
  643. continue;
  644. }
  645. /**
  646. * Stars
  647. */
  648. if (prev && (prev.type === 'globstar' || prev.star === true)) {
  649. prev.type = 'star';
  650. prev.star = true;
  651. prev.value += value;
  652. prev.output = star;
  653. state.backtrack = true;
  654. state.globstar = true;
  655. consume(value);
  656. continue;
  657. }
  658. let rest = remaining();
  659. if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
  660. extglobOpen('star', value);
  661. continue;
  662. }
  663. if (prev.type === 'star') {
  664. if (opts.noglobstar === true) {
  665. consume(value);
  666. continue;
  667. }
  668. const prior = prev.prev;
  669. const before = prior.prev;
  670. const isStart = prior.type === 'slash' || prior.type === 'bos';
  671. const afterStar = before && (before.type === 'star' || before.type === 'globstar');
  672. if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) {
  673. push({ type: 'star', value, output: '' });
  674. continue;
  675. }
  676. const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace');
  677. const isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren');
  678. if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) {
  679. push({ type: 'star', value, output: '' });
  680. continue;
  681. }
  682. // strip consecutive `/**/`
  683. while (rest.slice(0, 3) === '/**') {
  684. const after = input[state.index + 4];
  685. if (after && after !== '/') {
  686. break;
  687. }
  688. rest = rest.slice(3);
  689. consume('/**', 3);
  690. }
  691. if (prior.type === 'bos' && eos()) {
  692. prev.type = 'globstar';
  693. prev.value += value;
  694. prev.output = globstar(opts);
  695. state.output = prev.output;
  696. state.globstar = true;
  697. consume(value);
  698. continue;
  699. }
  700. if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) {
  701. state.output = state.output.slice(0, -(prior.output + prev.output).length);
  702. prior.output = `(?:${prior.output}`;
  703. prev.type = 'globstar';
  704. prev.output = globstar(opts) + (opts.strictSlashes ? ')' : '|$)');
  705. prev.value += value;
  706. state.globstar = true;
  707. state.output += prior.output + prev.output;
  708. consume(value);
  709. continue;
  710. }
  711. if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') {
  712. const end = rest[1] !== void 0 ? '|$' : '';
  713. state.output = state.output.slice(0, -(prior.output + prev.output).length);
  714. prior.output = `(?:${prior.output}`;
  715. prev.type = 'globstar';
  716. prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
  717. prev.value += value;
  718. state.output += prior.output + prev.output;
  719. state.globstar = true;
  720. consume(value + advance());
  721. push({ type: 'slash', value: '/', output: '' });
  722. continue;
  723. }
  724. if (prior.type === 'bos' && rest[0] === '/') {
  725. prev.type = 'globstar';
  726. prev.value += value;
  727. prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
  728. state.output = prev.output;
  729. state.globstar = true;
  730. consume(value + advance());
  731. push({ type: 'slash', value: '/', output: '' });
  732. continue;
  733. }
  734. // remove single star from output
  735. state.output = state.output.slice(0, -prev.output.length);
  736. // reset previous token to globstar
  737. prev.type = 'globstar';
  738. prev.output = globstar(opts);
  739. prev.value += value;
  740. // reset output with globstar
  741. state.output += prev.output;
  742. state.globstar = true;
  743. consume(value);
  744. continue;
  745. }
  746. const token = { type: 'star', value, output: star };
  747. if (opts.bash === true) {
  748. token.output = '.*?';
  749. if (prev.type === 'bos' || prev.type === 'slash') {
  750. token.output = nodot + token.output;
  751. }
  752. push(token);
  753. continue;
  754. }
  755. if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) {
  756. token.output = value;
  757. push(token);
  758. continue;
  759. }
  760. if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') {
  761. if (prev.type === 'dot') {
  762. state.output += NO_DOT_SLASH;
  763. prev.output += NO_DOT_SLASH;
  764. } else if (opts.dot === true) {
  765. state.output += NO_DOTS_SLASH;
  766. prev.output += NO_DOTS_SLASH;
  767. } else {
  768. state.output += nodot;
  769. prev.output += nodot;
  770. }
  771. if (peek() !== '*') {
  772. state.output += ONE_CHAR;
  773. prev.output += ONE_CHAR;
  774. }
  775. }
  776. push(token);
  777. }
  778. while (state.brackets > 0) {
  779. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']'));
  780. state.output = utils.escapeLast(state.output, '[');
  781. decrement('brackets');
  782. }
  783. while (state.parens > 0) {
  784. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')'));
  785. state.output = utils.escapeLast(state.output, '(');
  786. decrement('parens');
  787. }
  788. while (state.braces > 0) {
  789. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}'));
  790. state.output = utils.escapeLast(state.output, '{');
  791. decrement('braces');
  792. }
  793. if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) {
  794. push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` });
  795. }
  796. // rebuild the output if we had to backtrack at any point
  797. if (state.backtrack === true) {
  798. state.output = '';
  799. for (const token of state.tokens) {
  800. state.output += token.output != null ? token.output : token.value;
  801. if (token.suffix) {
  802. state.output += token.suffix;
  803. }
  804. }
  805. }
  806. return state;
  807. };
  808. /**
  809. * Fast paths for creating regular expressions for common glob patterns.
  810. * This can significantly speed up processing and has very little downside
  811. * impact when none of the fast paths match.
  812. */
  813. parse.fastpaths = (input, options) => {
  814. const opts = { ...options };
  815. const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
  816. const len = input.length;
  817. if (len > max) {
  818. throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
  819. }
  820. input = REPLACEMENTS[input] || input;
  821. const win32 = utils.isWindows(options);
  822. // create constants based on platform, for windows or posix
  823. const {
  824. DOT_LITERAL,
  825. SLASH_LITERAL,
  826. ONE_CHAR,
  827. DOTS_SLASH,
  828. NO_DOT,
  829. NO_DOTS,
  830. NO_DOTS_SLASH,
  831. STAR,
  832. START_ANCHOR
  833. } = constants.globChars(win32);
  834. const nodot = opts.dot ? NO_DOTS : NO_DOT;
  835. const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
  836. const capture = opts.capture ? '' : '?:';
  837. const state = { negated: false, prefix: '' };
  838. let star = opts.bash === true ? '.*?' : STAR;
  839. if (opts.capture) {
  840. star = `(${star})`;
  841. }
  842. const globstar = opts => {
  843. if (opts.noglobstar === true) return star;
  844. return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
  845. };
  846. const create = str => {
  847. switch (str) {
  848. case '*':
  849. return `${nodot}${ONE_CHAR}${star}`;
  850. case '.*':
  851. return `${DOT_LITERAL}${ONE_CHAR}${star}`;
  852. case '*.*':
  853. return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
  854. case '*/*':
  855. return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
  856. case '**':
  857. return nodot + globstar(opts);
  858. case '**/*':
  859. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
  860. case '**/*.*':
  861. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
  862. case '**/.*':
  863. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
  864. default: {
  865. const match = /^(.*?)\.(\w+)$/.exec(str);
  866. if (!match) return;
  867. const source = create(match[1]);
  868. if (!source) return;
  869. return source + DOT_LITERAL + match[2];
  870. }
  871. }
  872. };
  873. const output = utils.removePrefix(input, state);
  874. let source = create(output);
  875. if (source && opts.strictSlashes !== true) {
  876. source += `${SLASH_LITERAL}?`;
  877. }
  878. return source;
  879. };
  880. module.exports = parse;