parse.js 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078
  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' && !EXTGLOB_CHARS[tok.value]) {
  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. if (token.type === 'negate') {
  195. let extglobStar = star;
  196. if (token.inner && token.inner.length > 1 && token.inner.includes('/')) {
  197. extglobStar = globstar(opts);
  198. }
  199. if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) {
  200. output = token.close = `)$))${extglobStar}`;
  201. }
  202. if (token.prev.type === 'bos') {
  203. state.negatedExtglob = true;
  204. }
  205. }
  206. push({ type: 'paren', extglob: true, value, output });
  207. decrement('parens');
  208. };
  209. /**
  210. * Fast paths
  211. */
  212. if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
  213. let backslashes = false;
  214. let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
  215. if (first === '\\') {
  216. backslashes = true;
  217. return m;
  218. }
  219. if (first === '?') {
  220. if (esc) {
  221. return esc + first + (rest ? QMARK.repeat(rest.length) : '');
  222. }
  223. if (index === 0) {
  224. return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : '');
  225. }
  226. return QMARK.repeat(chars.length);
  227. }
  228. if (first === '.') {
  229. return DOT_LITERAL.repeat(chars.length);
  230. }
  231. if (first === '*') {
  232. if (esc) {
  233. return esc + first + (rest ? star : '');
  234. }
  235. return star;
  236. }
  237. return esc ? m : `\\${m}`;
  238. });
  239. if (backslashes === true) {
  240. if (opts.unescape === true) {
  241. output = output.replace(/\\/g, '');
  242. } else {
  243. output = output.replace(/\\+/g, m => {
  244. return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : '');
  245. });
  246. }
  247. }
  248. if (output === input && opts.contains === true) {
  249. state.output = input;
  250. return state;
  251. }
  252. state.output = utils.wrapOutput(output, state, options);
  253. return state;
  254. }
  255. /**
  256. * Tokenize input until we reach end-of-string
  257. */
  258. while (!eos()) {
  259. value = advance();
  260. if (value === '\u0000') {
  261. continue;
  262. }
  263. /**
  264. * Escaped characters
  265. */
  266. if (value === '\\') {
  267. const next = peek();
  268. if (next === '/' && opts.bash !== true) {
  269. continue;
  270. }
  271. if (next === '.' || next === ';') {
  272. continue;
  273. }
  274. if (!next) {
  275. value += '\\';
  276. push({ type: 'text', value });
  277. continue;
  278. }
  279. // collapse slashes to reduce potential for exploits
  280. const match = /^\\+/.exec(remaining());
  281. let slashes = 0;
  282. if (match && match[0].length > 2) {
  283. slashes = match[0].length;
  284. state.index += slashes;
  285. if (slashes % 2 !== 0) {
  286. value += '\\';
  287. }
  288. }
  289. if (opts.unescape === true) {
  290. value = advance() || '';
  291. } else {
  292. value += advance() || '';
  293. }
  294. if (state.brackets === 0) {
  295. push({ type: 'text', value });
  296. continue;
  297. }
  298. }
  299. /**
  300. * If we're inside a regex character class, continue
  301. * until we reach the closing bracket.
  302. */
  303. if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) {
  304. if (opts.posix !== false && value === ':') {
  305. const inner = prev.value.slice(1);
  306. if (inner.includes('[')) {
  307. prev.posix = true;
  308. if (inner.includes(':')) {
  309. const idx = prev.value.lastIndexOf('[');
  310. const pre = prev.value.slice(0, idx);
  311. const rest = prev.value.slice(idx + 2);
  312. const posix = POSIX_REGEX_SOURCE[rest];
  313. if (posix) {
  314. prev.value = pre + posix;
  315. state.backtrack = true;
  316. advance();
  317. if (!bos.output && tokens.indexOf(prev) === 1) {
  318. bos.output = ONE_CHAR;
  319. }
  320. continue;
  321. }
  322. }
  323. }
  324. }
  325. if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) {
  326. value = `\\${value}`;
  327. }
  328. if (value === ']' && (prev.value === '[' || prev.value === '[^')) {
  329. value = `\\${value}`;
  330. }
  331. if (opts.posix === true && value === '!' && prev.value === '[') {
  332. value = '^';
  333. }
  334. prev.value += value;
  335. append({ value });
  336. continue;
  337. }
  338. /**
  339. * If we're inside a quoted string, continue
  340. * until we reach the closing double quote.
  341. */
  342. if (state.quotes === 1 && value !== '"') {
  343. value = utils.escapeRegex(value);
  344. prev.value += value;
  345. append({ value });
  346. continue;
  347. }
  348. /**
  349. * Double quotes
  350. */
  351. if (value === '"') {
  352. state.quotes = state.quotes === 1 ? 0 : 1;
  353. if (opts.keepQuotes === true) {
  354. push({ type: 'text', value });
  355. }
  356. continue;
  357. }
  358. /**
  359. * Parentheses
  360. */
  361. if (value === '(') {
  362. increment('parens');
  363. push({ type: 'paren', value });
  364. continue;
  365. }
  366. if (value === ')') {
  367. if (state.parens === 0 && opts.strictBrackets === true) {
  368. throw new SyntaxError(syntaxError('opening', '('));
  369. }
  370. const extglob = extglobs[extglobs.length - 1];
  371. if (extglob && state.parens === extglob.parens + 1) {
  372. extglobClose(extglobs.pop());
  373. continue;
  374. }
  375. push({ type: 'paren', value, output: state.parens ? ')' : '\\)' });
  376. decrement('parens');
  377. continue;
  378. }
  379. /**
  380. * Square brackets
  381. */
  382. if (value === '[') {
  383. if (opts.nobracket === true || !remaining().includes(']')) {
  384. if (opts.nobracket !== true && opts.strictBrackets === true) {
  385. throw new SyntaxError(syntaxError('closing', ']'));
  386. }
  387. value = `\\${value}`;
  388. } else {
  389. increment('brackets');
  390. }
  391. push({ type: 'bracket', value });
  392. continue;
  393. }
  394. if (value === ']') {
  395. if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) {
  396. push({ type: 'text', value, output: `\\${value}` });
  397. continue;
  398. }
  399. if (state.brackets === 0) {
  400. if (opts.strictBrackets === true) {
  401. throw new SyntaxError(syntaxError('opening', '['));
  402. }
  403. push({ type: 'text', value, output: `\\${value}` });
  404. continue;
  405. }
  406. decrement('brackets');
  407. const prevValue = prev.value.slice(1);
  408. if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) {
  409. value = `/${value}`;
  410. }
  411. prev.value += value;
  412. append({ value });
  413. // when literal brackets are explicitly disabled
  414. // assume we should match with a regex character class
  415. if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) {
  416. continue;
  417. }
  418. const escaped = utils.escapeRegex(prev.value);
  419. state.output = state.output.slice(0, -prev.value.length);
  420. // when literal brackets are explicitly enabled
  421. // assume we should escape the brackets to match literal characters
  422. if (opts.literalBrackets === true) {
  423. state.output += escaped;
  424. prev.value = escaped;
  425. continue;
  426. }
  427. // when the user specifies nothing, try to match both
  428. prev.value = `(${capture}${escaped}|${prev.value})`;
  429. state.output += prev.value;
  430. continue;
  431. }
  432. /**
  433. * Braces
  434. */
  435. if (value === '{' && opts.nobrace !== true) {
  436. increment('braces');
  437. const open = {
  438. type: 'brace',
  439. value,
  440. output: '(',
  441. outputIndex: state.output.length,
  442. tokensIndex: state.tokens.length
  443. };
  444. braces.push(open);
  445. push(open);
  446. continue;
  447. }
  448. if (value === '}') {
  449. const brace = braces[braces.length - 1];
  450. if (opts.nobrace === true || !brace) {
  451. push({ type: 'text', value, output: value });
  452. continue;
  453. }
  454. let output = ')';
  455. if (brace.dots === true) {
  456. const arr = tokens.slice();
  457. const range = [];
  458. for (let i = arr.length - 1; i >= 0; i--) {
  459. tokens.pop();
  460. if (arr[i].type === 'brace') {
  461. break;
  462. }
  463. if (arr[i].type !== 'dots') {
  464. range.unshift(arr[i].value);
  465. }
  466. }
  467. output = expandRange(range, opts);
  468. state.backtrack = true;
  469. }
  470. if (brace.comma !== true && brace.dots !== true) {
  471. const out = state.output.slice(0, brace.outputIndex);
  472. const toks = state.tokens.slice(brace.tokensIndex);
  473. brace.value = brace.output = '\\{';
  474. value = output = '\\}';
  475. state.output = out;
  476. for (const t of toks) {
  477. state.output += (t.output || t.value);
  478. }
  479. }
  480. push({ type: 'brace', value, output });
  481. decrement('braces');
  482. braces.pop();
  483. continue;
  484. }
  485. /**
  486. * Pipes
  487. */
  488. if (value === '|') {
  489. if (extglobs.length > 0) {
  490. extglobs[extglobs.length - 1].conditions++;
  491. }
  492. push({ type: 'text', value });
  493. continue;
  494. }
  495. /**
  496. * Commas
  497. */
  498. if (value === ',') {
  499. let output = value;
  500. const brace = braces[braces.length - 1];
  501. if (brace && stack[stack.length - 1] === 'braces') {
  502. brace.comma = true;
  503. output = '|';
  504. }
  505. push({ type: 'comma', value, output });
  506. continue;
  507. }
  508. /**
  509. * Slashes
  510. */
  511. if (value === '/') {
  512. // if the beginning of the glob is "./", advance the start
  513. // to the current index, and don't add the "./" characters
  514. // to the state. This greatly simplifies lookbehinds when
  515. // checking for BOS characters like "!" and "." (not "./")
  516. if (prev.type === 'dot' && state.index === state.start + 1) {
  517. state.start = state.index + 1;
  518. state.consumed = '';
  519. state.output = '';
  520. tokens.pop();
  521. prev = bos; // reset "prev" to the first token
  522. continue;
  523. }
  524. push({ type: 'slash', value, output: SLASH_LITERAL });
  525. continue;
  526. }
  527. /**
  528. * Dots
  529. */
  530. if (value === '.') {
  531. if (state.braces > 0 && prev.type === 'dot') {
  532. if (prev.value === '.') prev.output = DOT_LITERAL;
  533. const brace = braces[braces.length - 1];
  534. prev.type = 'dots';
  535. prev.output += value;
  536. prev.value += value;
  537. brace.dots = true;
  538. continue;
  539. }
  540. if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') {
  541. push({ type: 'text', value, output: DOT_LITERAL });
  542. continue;
  543. }
  544. push({ type: 'dot', value, output: DOT_LITERAL });
  545. continue;
  546. }
  547. /**
  548. * Question marks
  549. */
  550. if (value === '?') {
  551. const isGroup = prev && prev.value === '(';
  552. if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  553. extglobOpen('qmark', value);
  554. continue;
  555. }
  556. if (prev && prev.type === 'paren') {
  557. const next = peek();
  558. let output = value;
  559. if (next === '<' && !utils.supportsLookbehinds()) {
  560. throw new Error('Node.js v10 or higher is required for regex lookbehinds');
  561. }
  562. if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) {
  563. output = `\\${value}`;
  564. }
  565. push({ type: 'text', value, output });
  566. continue;
  567. }
  568. if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) {
  569. push({ type: 'qmark', value, output: QMARK_NO_DOT });
  570. continue;
  571. }
  572. push({ type: 'qmark', value, output: QMARK });
  573. continue;
  574. }
  575. /**
  576. * Exclamation
  577. */
  578. if (value === '!') {
  579. if (opts.noextglob !== true && peek() === '(') {
  580. if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) {
  581. extglobOpen('negate', value);
  582. continue;
  583. }
  584. }
  585. if (opts.nonegate !== true && state.index === 0) {
  586. negate();
  587. continue;
  588. }
  589. }
  590. /**
  591. * Plus
  592. */
  593. if (value === '+') {
  594. if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  595. extglobOpen('plus', value);
  596. continue;
  597. }
  598. if ((prev && prev.value === '(') || opts.regex === false) {
  599. push({ type: 'plus', value, output: PLUS_LITERAL });
  600. continue;
  601. }
  602. if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) {
  603. push({ type: 'plus', value });
  604. continue;
  605. }
  606. push({ type: 'plus', value: PLUS_LITERAL });
  607. continue;
  608. }
  609. /**
  610. * Plain text
  611. */
  612. if (value === '@') {
  613. if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  614. push({ type: 'at', extglob: true, value, output: '' });
  615. continue;
  616. }
  617. push({ type: 'text', value });
  618. continue;
  619. }
  620. /**
  621. * Plain text
  622. */
  623. if (value !== '*') {
  624. if (value === '$' || value === '^') {
  625. value = `\\${value}`;
  626. }
  627. const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
  628. if (match) {
  629. value += match[0];
  630. state.index += match[0].length;
  631. }
  632. push({ type: 'text', value });
  633. continue;
  634. }
  635. /**
  636. * Stars
  637. */
  638. if (prev && (prev.type === 'globstar' || prev.star === true)) {
  639. prev.type = 'star';
  640. prev.star = true;
  641. prev.value += value;
  642. prev.output = star;
  643. state.backtrack = true;
  644. state.globstar = true;
  645. consume(value);
  646. continue;
  647. }
  648. let rest = remaining();
  649. if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
  650. extglobOpen('star', value);
  651. continue;
  652. }
  653. if (prev.type === 'star') {
  654. if (opts.noglobstar === true) {
  655. consume(value);
  656. continue;
  657. }
  658. const prior = prev.prev;
  659. const before = prior.prev;
  660. const isStart = prior.type === 'slash' || prior.type === 'bos';
  661. const afterStar = before && (before.type === 'star' || before.type === 'globstar');
  662. if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) {
  663. push({ type: 'star', value, output: '' });
  664. continue;
  665. }
  666. const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace');
  667. const isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren');
  668. if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) {
  669. push({ type: 'star', value, output: '' });
  670. continue;
  671. }
  672. // strip consecutive `/**/`
  673. while (rest.slice(0, 3) === '/**') {
  674. const after = input[state.index + 4];
  675. if (after && after !== '/') {
  676. break;
  677. }
  678. rest = rest.slice(3);
  679. consume('/**', 3);
  680. }
  681. if (prior.type === 'bos' && eos()) {
  682. prev.type = 'globstar';
  683. prev.value += value;
  684. prev.output = globstar(opts);
  685. state.output = prev.output;
  686. state.globstar = true;
  687. consume(value);
  688. continue;
  689. }
  690. if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) {
  691. state.output = state.output.slice(0, -(prior.output + prev.output).length);
  692. prior.output = `(?:${prior.output}`;
  693. prev.type = 'globstar';
  694. prev.output = globstar(opts) + (opts.strictSlashes ? ')' : '|$)');
  695. prev.value += value;
  696. state.globstar = true;
  697. state.output += prior.output + prev.output;
  698. consume(value);
  699. continue;
  700. }
  701. if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') {
  702. const end = rest[1] !== void 0 ? '|$' : '';
  703. state.output = state.output.slice(0, -(prior.output + prev.output).length);
  704. prior.output = `(?:${prior.output}`;
  705. prev.type = 'globstar';
  706. prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
  707. prev.value += value;
  708. state.output += prior.output + prev.output;
  709. state.globstar = true;
  710. consume(value + advance());
  711. push({ type: 'slash', value: '/', output: '' });
  712. continue;
  713. }
  714. if (prior.type === 'bos' && rest[0] === '/') {
  715. prev.type = 'globstar';
  716. prev.value += value;
  717. prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
  718. state.output = prev.output;
  719. state.globstar = true;
  720. consume(value + advance());
  721. push({ type: 'slash', value: '/', output: '' });
  722. continue;
  723. }
  724. // remove single star from output
  725. state.output = state.output.slice(0, -prev.output.length);
  726. // reset previous token to globstar
  727. prev.type = 'globstar';
  728. prev.output = globstar(opts);
  729. prev.value += value;
  730. // reset output with globstar
  731. state.output += prev.output;
  732. state.globstar = true;
  733. consume(value);
  734. continue;
  735. }
  736. const token = { type: 'star', value, output: star };
  737. if (opts.bash === true) {
  738. token.output = '.*?';
  739. if (prev.type === 'bos' || prev.type === 'slash') {
  740. token.output = nodot + token.output;
  741. }
  742. push(token);
  743. continue;
  744. }
  745. if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) {
  746. token.output = value;
  747. push(token);
  748. continue;
  749. }
  750. if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') {
  751. if (prev.type === 'dot') {
  752. state.output += NO_DOT_SLASH;
  753. prev.output += NO_DOT_SLASH;
  754. } else if (opts.dot === true) {
  755. state.output += NO_DOTS_SLASH;
  756. prev.output += NO_DOTS_SLASH;
  757. } else {
  758. state.output += nodot;
  759. prev.output += nodot;
  760. }
  761. if (peek() !== '*') {
  762. state.output += ONE_CHAR;
  763. prev.output += ONE_CHAR;
  764. }
  765. }
  766. push(token);
  767. }
  768. while (state.brackets > 0) {
  769. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']'));
  770. state.output = utils.escapeLast(state.output, '[');
  771. decrement('brackets');
  772. }
  773. while (state.parens > 0) {
  774. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')'));
  775. state.output = utils.escapeLast(state.output, '(');
  776. decrement('parens');
  777. }
  778. while (state.braces > 0) {
  779. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}'));
  780. state.output = utils.escapeLast(state.output, '{');
  781. decrement('braces');
  782. }
  783. if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) {
  784. push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` });
  785. }
  786. // rebuild the output if we had to backtrack at any point
  787. if (state.backtrack === true) {
  788. state.output = '';
  789. for (const token of state.tokens) {
  790. state.output += token.output != null ? token.output : token.value;
  791. if (token.suffix) {
  792. state.output += token.suffix;
  793. }
  794. }
  795. }
  796. return state;
  797. };
  798. /**
  799. * Fast paths for creating regular expressions for common glob patterns.
  800. * This can significantly speed up processing and has very little downside
  801. * impact when none of the fast paths match.
  802. */
  803. parse.fastpaths = (input, options) => {
  804. const opts = { ...options };
  805. const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
  806. const len = input.length;
  807. if (len > max) {
  808. throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
  809. }
  810. input = REPLACEMENTS[input] || input;
  811. const win32 = utils.isWindows(options);
  812. // create constants based on platform, for windows or posix
  813. const {
  814. DOT_LITERAL,
  815. SLASH_LITERAL,
  816. ONE_CHAR,
  817. DOTS_SLASH,
  818. NO_DOT,
  819. NO_DOTS,
  820. NO_DOTS_SLASH,
  821. STAR,
  822. START_ANCHOR
  823. } = constants.globChars(win32);
  824. const nodot = opts.dot ? NO_DOTS : NO_DOT;
  825. const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
  826. const capture = opts.capture ? '' : '?:';
  827. const state = { negated: false, prefix: '' };
  828. let star = opts.bash === true ? '.*?' : STAR;
  829. if (opts.capture) {
  830. star = `(${star})`;
  831. }
  832. const globstar = (opts) => {
  833. if (opts.noglobstar === true) return star;
  834. return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
  835. };
  836. const create = str => {
  837. switch (str) {
  838. case '*':
  839. return `${nodot}${ONE_CHAR}${star}`;
  840. case '.*':
  841. return `${DOT_LITERAL}${ONE_CHAR}${star}`;
  842. case '*.*':
  843. return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
  844. case '*/*':
  845. return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
  846. case '**':
  847. return nodot + globstar(opts);
  848. case '**/*':
  849. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
  850. case '**/*.*':
  851. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
  852. case '**/.*':
  853. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
  854. default: {
  855. const match = /^(.*?)\.(\w+)$/.exec(str);
  856. if (!match) return;
  857. const source = create(match[1]);
  858. if (!source) return;
  859. return source + DOT_LITERAL + match[2];
  860. }
  861. }
  862. };
  863. const output = utils.removePrefix(input, state);
  864. let source = create(output);
  865. if (source && opts.strictSlashes !== true) {
  866. source += `${SLASH_LITERAL}?`;
  867. }
  868. return source;
  869. };
  870. module.exports = parse;