parse.js 27 KB

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