parse.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425
  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. const splitTopLevel = input => {
  38. const parts = [];
  39. let bracket = 0;
  40. let paren = 0;
  41. let quote = 0;
  42. let value = '';
  43. let escaped = false;
  44. for (const ch of input) {
  45. if (escaped === true) {
  46. value += ch;
  47. escaped = false;
  48. continue;
  49. }
  50. if (ch === '\\') {
  51. value += ch;
  52. escaped = true;
  53. continue;
  54. }
  55. if (ch === '"') {
  56. quote = quote === 1 ? 0 : 1;
  57. value += ch;
  58. continue;
  59. }
  60. if (quote === 0) {
  61. if (ch === '[') {
  62. bracket++;
  63. } else if (ch === ']' && bracket > 0) {
  64. bracket--;
  65. } else if (bracket === 0) {
  66. if (ch === '(') {
  67. paren++;
  68. } else if (ch === ')' && paren > 0) {
  69. paren--;
  70. } else if (ch === '|' && paren === 0) {
  71. parts.push(value);
  72. value = '';
  73. continue;
  74. }
  75. }
  76. }
  77. value += ch;
  78. }
  79. parts.push(value);
  80. return parts;
  81. };
  82. const isPlainBranch = branch => {
  83. let escaped = false;
  84. for (const ch of branch) {
  85. if (escaped === true) {
  86. escaped = false;
  87. continue;
  88. }
  89. if (ch === '\\') {
  90. escaped = true;
  91. continue;
  92. }
  93. if (/[?*+@!()[\]{}]/.test(ch)) {
  94. return false;
  95. }
  96. }
  97. return true;
  98. };
  99. const normalizeSimpleBranch = branch => {
  100. let value = branch.trim();
  101. let changed = true;
  102. while (changed === true) {
  103. changed = false;
  104. if (/^@\([^\\()[\]{}|]+\)$/.test(value)) {
  105. value = value.slice(2, -1);
  106. changed = true;
  107. }
  108. }
  109. if (!isPlainBranch(value)) {
  110. return;
  111. }
  112. return value.replace(/\\(.)/g, '$1');
  113. };
  114. const hasRepeatedCharPrefixOverlap = branches => {
  115. const values = branches.map(normalizeSimpleBranch).filter(Boolean);
  116. for (let i = 0; i < values.length; i++) {
  117. for (let j = i + 1; j < values.length; j++) {
  118. const a = values[i];
  119. const b = values[j];
  120. const char = a[0];
  121. if (!char || a !== char.repeat(a.length) || b !== char.repeat(b.length)) {
  122. continue;
  123. }
  124. if (a === b || a.startsWith(b) || b.startsWith(a)) {
  125. return true;
  126. }
  127. }
  128. }
  129. return false;
  130. };
  131. const parseRepeatedExtglob = (pattern, requireEnd = true) => {
  132. if ((pattern[0] !== '+' && pattern[0] !== '*') || pattern[1] !== '(') {
  133. return;
  134. }
  135. let bracket = 0;
  136. let paren = 0;
  137. let quote = 0;
  138. let escaped = false;
  139. for (let i = 1; i < pattern.length; i++) {
  140. const ch = pattern[i];
  141. if (escaped === true) {
  142. escaped = false;
  143. continue;
  144. }
  145. if (ch === '\\') {
  146. escaped = true;
  147. continue;
  148. }
  149. if (ch === '"') {
  150. quote = quote === 1 ? 0 : 1;
  151. continue;
  152. }
  153. if (quote === 1) {
  154. continue;
  155. }
  156. if (ch === '[') {
  157. bracket++;
  158. continue;
  159. }
  160. if (ch === ']' && bracket > 0) {
  161. bracket--;
  162. continue;
  163. }
  164. if (bracket > 0) {
  165. continue;
  166. }
  167. if (ch === '(') {
  168. paren++;
  169. continue;
  170. }
  171. if (ch === ')') {
  172. paren--;
  173. if (paren === 0) {
  174. if (requireEnd === true && i !== pattern.length - 1) {
  175. return;
  176. }
  177. return {
  178. type: pattern[0],
  179. body: pattern.slice(2, i),
  180. end: i
  181. };
  182. }
  183. }
  184. }
  185. };
  186. const buildCharClassStar = chars => {
  187. const source = chars.length === 1
  188. ? utils.escapeRegex(chars[0])
  189. : `[${chars.map(ch => utils.escapeRegex(ch)).join('')}]`;
  190. return `${source}*`;
  191. };
  192. const getStarExtglobSequenceChars = pattern => {
  193. let index = 0;
  194. const chars = [];
  195. while (index < pattern.length) {
  196. const match = parseRepeatedExtglob(pattern.slice(index), false);
  197. if (!match || match.type !== '*') {
  198. return;
  199. }
  200. const branches = splitTopLevel(match.body).map(branch => branch.trim());
  201. if (branches.length !== 1) {
  202. return;
  203. }
  204. const branch = normalizeSimpleBranch(branches[0]);
  205. if (!branch || branch.length !== 1) {
  206. return;
  207. }
  208. chars.push(branch);
  209. index += match.end + 1;
  210. }
  211. if (chars.length < 1) {
  212. return;
  213. }
  214. return chars;
  215. };
  216. const repeatedExtglobRecursion = pattern => {
  217. let depth = 0;
  218. let value = pattern.trim();
  219. let match = parseRepeatedExtglob(value);
  220. while (match) {
  221. depth++;
  222. value = match.body.trim();
  223. match = parseRepeatedExtglob(value);
  224. }
  225. return depth;
  226. };
  227. const analyzeRepeatedExtglob = (body, options) => {
  228. if (options.maxExtglobRecursion === false) {
  229. return { risky: false };
  230. }
  231. const max =
  232. typeof options.maxExtglobRecursion === 'number'
  233. ? options.maxExtglobRecursion
  234. : constants.DEFAULT_MAX_EXTGLOB_RECURSION;
  235. const branches = splitTopLevel(body).map(branch => branch.trim());
  236. if (branches.length > 1) {
  237. if (
  238. branches.some(branch => branch === '') ||
  239. branches.some(branch => /^[*?]+$/.test(branch)) ||
  240. hasRepeatedCharPrefixOverlap(branches)
  241. ) {
  242. return { risky: true };
  243. }
  244. }
  245. // A repeated extglob is "risky" (prone to catastrophic backtracking) when a
  246. // branch is itself a `*(...)` sequence, since that nests an unbounded quantifier
  247. // inside the outer `+(...)`/`*(...)`. When *every* branch reduces to single
  248. // characters we can emit one flat, ReDoS-safe character class that preserves the
  249. // meaning of ALL branches (e.g. `+(*(a)|*(b))` -> `[ab]*`), rather than dropping
  250. // every branch but the first.
  251. const safeChars = [];
  252. let sawStarSequence = false;
  253. let combinable = true;
  254. for (const branch of branches) {
  255. const chars = getStarExtglobSequenceChars(branch);
  256. if (chars) {
  257. sawStarSequence = true;
  258. safeChars.push(...chars);
  259. continue;
  260. }
  261. const literal = normalizeSimpleBranch(branch);
  262. if (literal && literal.length === 1) {
  263. safeChars.push(literal);
  264. continue;
  265. }
  266. combinable = false;
  267. if (repeatedExtglobRecursion(branch) > max) {
  268. return { risky: true };
  269. }
  270. }
  271. if (sawStarSequence) {
  272. return combinable
  273. ? { risky: true, safeOutput: buildCharClassStar([...new Set(safeChars)]) }
  274. : { risky: true };
  275. }
  276. return { risky: false };
  277. };
  278. /**
  279. * Parse the given input string.
  280. * @param {String} input
  281. * @param {Object} options
  282. * @return {Object}
  283. */
  284. const parse = (input, options) => {
  285. if (typeof input !== 'string') {
  286. throw new TypeError('Expected a string');
  287. }
  288. input = REPLACEMENTS[input] || input;
  289. const opts = { ...options };
  290. const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
  291. let len = input.length;
  292. if (len > max) {
  293. throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
  294. }
  295. const bos = { type: 'bos', value: '', output: opts.prepend || '' };
  296. const tokens = [bos];
  297. const capture = opts.capture ? '' : '?:';
  298. // create constants based on platform, for windows or posix
  299. const PLATFORM_CHARS = constants.globChars(opts.windows);
  300. const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
  301. const {
  302. DOT_LITERAL,
  303. PLUS_LITERAL,
  304. SLASH_LITERAL,
  305. ONE_CHAR,
  306. DOTS_SLASH,
  307. NO_DOT,
  308. NO_DOT_SLASH,
  309. NO_DOTS_SLASH,
  310. QMARK,
  311. QMARK_NO_DOT,
  312. STAR,
  313. START_ANCHOR
  314. } = PLATFORM_CHARS;
  315. const globstar = opts => {
  316. return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
  317. };
  318. const nodot = opts.dot ? '' : NO_DOT;
  319. const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
  320. let star = opts.bash === true ? globstar(opts) : STAR;
  321. if (opts.capture) {
  322. star = `(${star})`;
  323. }
  324. // minimatch options support
  325. if (typeof opts.noext === 'boolean') {
  326. opts.noextglob = opts.noext;
  327. }
  328. const state = {
  329. input,
  330. index: -1,
  331. start: 0,
  332. dot: opts.dot === true,
  333. consumed: '',
  334. output: '',
  335. prefix: '',
  336. backtrack: false,
  337. negated: false,
  338. brackets: 0,
  339. braces: 0,
  340. parens: 0,
  341. quotes: 0,
  342. globstar: false,
  343. tokens
  344. };
  345. input = utils.removePrefix(input, state);
  346. len = input.length;
  347. const extglobs = [];
  348. const braces = [];
  349. const stack = [];
  350. let prev = bos;
  351. let value;
  352. /**
  353. * Tokenizing helpers
  354. */
  355. const eos = () => state.index === len - 1;
  356. const peek = state.peek = (n = 1) => input[state.index + n];
  357. const advance = state.advance = () => input[++state.index] || '';
  358. const remaining = () => input.slice(state.index + 1);
  359. const consume = (value = '', num = 0) => {
  360. state.consumed += value;
  361. state.index += num;
  362. };
  363. const append = token => {
  364. state.output += token.output != null ? token.output : token.value;
  365. consume(token.value);
  366. };
  367. const negate = () => {
  368. let count = 1;
  369. while (peek() === '!' && (peek(2) !== '(' || peek(3) === '?')) {
  370. advance();
  371. state.start++;
  372. count++;
  373. }
  374. if (count % 2 === 0) {
  375. return false;
  376. }
  377. state.negated = true;
  378. state.start++;
  379. return true;
  380. };
  381. const increment = type => {
  382. state[type]++;
  383. stack.push(type);
  384. };
  385. const decrement = type => {
  386. state[type]--;
  387. stack.pop();
  388. };
  389. /**
  390. * Push tokens onto the tokens array. This helper speeds up
  391. * tokenizing by 1) helping us avoid backtracking as much as possible,
  392. * and 2) helping us avoid creating extra tokens when consecutive
  393. * characters are plain text. This improves performance and simplifies
  394. * lookbehinds.
  395. */
  396. const push = tok => {
  397. if (prev.type === 'globstar') {
  398. const isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace');
  399. const isExtglob = tok.extglob === true || (extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'));
  400. if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) {
  401. state.output = state.output.slice(0, -prev.output.length);
  402. prev.type = 'star';
  403. prev.value = '*';
  404. prev.output = star;
  405. state.output += prev.output;
  406. }
  407. }
  408. if (extglobs.length && tok.type !== 'paren') {
  409. extglobs[extglobs.length - 1].inner += tok.value;
  410. }
  411. if (tok.value || tok.output) append(tok);
  412. if (prev && prev.type === 'text' && tok.type === 'text') {
  413. prev.output = (prev.output || prev.value) + tok.value;
  414. prev.value += tok.value;
  415. return;
  416. }
  417. tok.prev = prev;
  418. tokens.push(tok);
  419. prev = tok;
  420. };
  421. const extglobOpen = (type, value) => {
  422. const token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' };
  423. token.prev = prev;
  424. token.parens = state.parens;
  425. token.output = state.output;
  426. token.startIndex = state.index;
  427. token.tokensIndex = tokens.length;
  428. const output = (opts.capture ? '(' : '') + token.open;
  429. increment('parens');
  430. push({ type, value, output: state.output ? '' : ONE_CHAR });
  431. push({ type: 'paren', extglob: true, value: advance(), output });
  432. extglobs.push(token);
  433. };
  434. const extglobClose = token => {
  435. const literal = input.slice(token.startIndex, state.index + 1);
  436. const body = input.slice(token.startIndex + 2, state.index);
  437. const analysis = analyzeRepeatedExtglob(body, opts);
  438. if ((token.type === 'plus' || token.type === 'star') && analysis.risky) {
  439. const safeOutput = analysis.safeOutput
  440. ? (token.output ? '' : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput)
  441. : undefined;
  442. const open = tokens[token.tokensIndex];
  443. open.type = 'text';
  444. open.value = literal;
  445. open.output = safeOutput || utils.escapeRegex(literal);
  446. for (let i = token.tokensIndex + 1; i < tokens.length; i++) {
  447. tokens[i].value = '';
  448. tokens[i].output = '';
  449. delete tokens[i].suffix;
  450. }
  451. state.output = token.output + open.output;
  452. state.backtrack = true;
  453. push({ type: 'paren', extglob: true, value, output: '' });
  454. decrement('parens');
  455. return;
  456. }
  457. let output = token.close + (opts.capture ? ')' : '');
  458. let rest;
  459. if (token.type === 'negate') {
  460. let extglobStar = star;
  461. if (token.inner && token.inner.length > 1 && token.inner.includes('/')) {
  462. extglobStar = globstar(opts);
  463. }
  464. if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) {
  465. output = token.close = `)$))${extglobStar}`;
  466. }
  467. if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
  468. // Any non-magical string (`.ts`) or even nested expression (`.{ts,tsx}`) can follow after the closing parenthesis.
  469. // In this case, we need to parse the string and use it in the output of the original pattern.
  470. // Suitable patterns: `/!(*.d).ts`, `/!(*.d).{ts,tsx}`, `**/!(*-dbg).@(js)`.
  471. //
  472. // Disabling the `fastpaths` option due to a problem with parsing strings as `.ts` in the pattern like `**/!(*.d).ts`.
  473. const expression = parse(rest, { ...options, fastpaths: false }).output;
  474. output = token.close = `)${expression})${extglobStar})`;
  475. }
  476. if (token.prev.type === 'bos') {
  477. state.negatedExtglob = true;
  478. }
  479. }
  480. push({ type: 'paren', extglob: true, value, output });
  481. decrement('parens');
  482. };
  483. /**
  484. * Fast paths
  485. */
  486. if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
  487. let backslashes = false;
  488. let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
  489. if (first === '\\') {
  490. backslashes = true;
  491. return m;
  492. }
  493. if (first === '?') {
  494. if (esc) {
  495. return esc + first + (rest ? QMARK.repeat(rest.length) : '');
  496. }
  497. if (index === 0) {
  498. return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : '');
  499. }
  500. return QMARK.repeat(chars.length);
  501. }
  502. if (first === '.') {
  503. return DOT_LITERAL.repeat(chars.length);
  504. }
  505. if (first === '*') {
  506. if (esc) {
  507. return esc + first + (rest ? star : '');
  508. }
  509. return star;
  510. }
  511. return esc ? m : `\\${m}`;
  512. });
  513. if (backslashes === true) {
  514. if (opts.unescape === true) {
  515. output = output.replace(/\\/g, '');
  516. } else {
  517. output = output.replace(/\\+/g, m => {
  518. return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : '');
  519. });
  520. }
  521. }
  522. if (output === input && opts.contains === true) {
  523. state.output = input;
  524. return state;
  525. }
  526. state.output = utils.wrapOutput(output, state, options);
  527. return state;
  528. }
  529. /**
  530. * Tokenize input until we reach end-of-string
  531. */
  532. while (!eos()) {
  533. value = advance();
  534. if (value === '\u0000') {
  535. continue;
  536. }
  537. /**
  538. * Escaped characters
  539. */
  540. if (value === '\\') {
  541. const next = peek();
  542. if (next === '/' && opts.bash !== true) {
  543. continue;
  544. }
  545. if (next === '.' || next === ';') {
  546. continue;
  547. }
  548. if (!next) {
  549. value += '\\';
  550. push({ type: 'text', value });
  551. continue;
  552. }
  553. // collapse slashes to reduce potential for exploits
  554. const match = /^\\+/.exec(remaining());
  555. let slashes = 0;
  556. if (match && match[0].length > 2) {
  557. slashes = match[0].length;
  558. state.index += slashes;
  559. if (slashes % 2 !== 0) {
  560. value += '\\';
  561. }
  562. }
  563. if (opts.unescape === true) {
  564. value = advance();
  565. } else {
  566. value += advance();
  567. }
  568. if (state.brackets === 0) {
  569. push({ type: 'text', value });
  570. continue;
  571. }
  572. }
  573. /**
  574. * If we're inside a regex character class, continue
  575. * until we reach the closing bracket.
  576. */
  577. if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) {
  578. if (opts.posix !== false && value === ':') {
  579. const inner = prev.value.slice(1);
  580. if (inner.includes('[')) {
  581. prev.posix = true;
  582. if (inner.includes(':')) {
  583. const idx = prev.value.lastIndexOf('[');
  584. const pre = prev.value.slice(0, idx);
  585. const rest = prev.value.slice(idx + 2);
  586. const posix = POSIX_REGEX_SOURCE[rest];
  587. if (posix) {
  588. prev.value = pre + posix;
  589. state.backtrack = true;
  590. advance();
  591. if (!bos.output && tokens.indexOf(prev) === 1) {
  592. bos.output = ONE_CHAR;
  593. }
  594. continue;
  595. }
  596. }
  597. }
  598. }
  599. if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) {
  600. value = `\\${value}`;
  601. }
  602. if (value === ']' && (prev.value === '[' || prev.value === '[^')) {
  603. value = `\\${value}`;
  604. }
  605. if (opts.posix === true && value === '!' && prev.value === '[') {
  606. value = '^';
  607. }
  608. prev.value += value;
  609. append({ value });
  610. continue;
  611. }
  612. /**
  613. * If we're inside a quoted string, continue
  614. * until we reach the closing double quote.
  615. */
  616. if (state.quotes === 1 && value !== '"') {
  617. value = utils.escapeRegex(value);
  618. prev.value += value;
  619. append({ value });
  620. continue;
  621. }
  622. /**
  623. * Double quotes
  624. */
  625. if (value === '"') {
  626. state.quotes = state.quotes === 1 ? 0 : 1;
  627. if (opts.keepQuotes === true) {
  628. push({ type: 'text', value });
  629. }
  630. continue;
  631. }
  632. /**
  633. * Parentheses
  634. */
  635. if (value === '(') {
  636. increment('parens');
  637. push({ type: 'paren', value });
  638. continue;
  639. }
  640. if (value === ')') {
  641. if (state.parens === 0 && opts.strictBrackets === true) {
  642. throw new SyntaxError(syntaxError('opening', '('));
  643. }
  644. const extglob = extglobs[extglobs.length - 1];
  645. if (extglob && state.parens === extglob.parens + 1) {
  646. extglobClose(extglobs.pop());
  647. continue;
  648. }
  649. push({ type: 'paren', value, output: state.parens ? ')' : '\\)' });
  650. decrement('parens');
  651. continue;
  652. }
  653. /**
  654. * Square brackets
  655. */
  656. if (value === '[') {
  657. if (opts.nobracket === true || !remaining().includes(']')) {
  658. if (opts.nobracket !== true && opts.strictBrackets === true) {
  659. throw new SyntaxError(syntaxError('closing', ']'));
  660. }
  661. value = `\\${value}`;
  662. } else {
  663. increment('brackets');
  664. }
  665. push({ type: 'bracket', value });
  666. continue;
  667. }
  668. if (value === ']') {
  669. if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) {
  670. push({ type: 'text', value, output: `\\${value}` });
  671. continue;
  672. }
  673. if (state.brackets === 0) {
  674. if (opts.strictBrackets === true) {
  675. throw new SyntaxError(syntaxError('opening', '['));
  676. }
  677. push({ type: 'text', value, output: `\\${value}` });
  678. continue;
  679. }
  680. decrement('brackets');
  681. const prevValue = prev.value.slice(1);
  682. if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) {
  683. value = `/${value}`;
  684. }
  685. prev.value += value;
  686. append({ value });
  687. // when literal brackets are explicitly disabled
  688. // assume we should match with a regex character class
  689. if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) {
  690. continue;
  691. }
  692. const escaped = utils.escapeRegex(prev.value);
  693. state.output = state.output.slice(0, -prev.value.length);
  694. // when literal brackets are explicitly enabled
  695. // assume we should escape the brackets to match literal characters
  696. if (opts.literalBrackets === true) {
  697. state.output += escaped;
  698. prev.value = escaped;
  699. continue;
  700. }
  701. // when the user specifies nothing, try to match both
  702. prev.value = `(${capture}${escaped}|${prev.value})`;
  703. state.output += prev.value;
  704. continue;
  705. }
  706. /**
  707. * Braces
  708. */
  709. if (value === '{' && opts.nobrace !== true) {
  710. increment('braces');
  711. const open = {
  712. type: 'brace',
  713. value,
  714. output: '(',
  715. outputIndex: state.output.length,
  716. tokensIndex: state.tokens.length
  717. };
  718. braces.push(open);
  719. push(open);
  720. continue;
  721. }
  722. if (value === '}') {
  723. const brace = braces[braces.length - 1];
  724. if (opts.nobrace === true || !brace) {
  725. push({ type: 'text', value, output: value });
  726. continue;
  727. }
  728. let output = ')';
  729. if (brace.dots === true) {
  730. const arr = tokens.slice();
  731. const range = [];
  732. for (let i = arr.length - 1; i >= 0; i--) {
  733. tokens.pop();
  734. if (arr[i].type === 'brace') {
  735. break;
  736. }
  737. if (arr[i].type !== 'dots') {
  738. range.unshift(arr[i].value);
  739. }
  740. }
  741. output = expandRange(range, opts);
  742. state.backtrack = true;
  743. }
  744. if (brace.comma !== true && brace.dots !== true) {
  745. const out = state.output.slice(0, brace.outputIndex);
  746. const toks = state.tokens.slice(brace.tokensIndex);
  747. brace.value = brace.output = '\\{';
  748. value = output = '\\}';
  749. state.output = out;
  750. for (const t of toks) {
  751. state.output += (t.output || t.value);
  752. }
  753. }
  754. push({ type: 'brace', value, output });
  755. decrement('braces');
  756. braces.pop();
  757. continue;
  758. }
  759. /**
  760. * Pipes
  761. */
  762. if (value === '|') {
  763. if (extglobs.length > 0) {
  764. extglobs[extglobs.length - 1].conditions++;
  765. }
  766. push({ type: 'text', value });
  767. continue;
  768. }
  769. /**
  770. * Commas
  771. */
  772. if (value === ',') {
  773. let output = value;
  774. const brace = braces[braces.length - 1];
  775. if (brace && stack[stack.length - 1] === 'braces') {
  776. brace.comma = true;
  777. output = '|';
  778. }
  779. push({ type: 'comma', value, output });
  780. continue;
  781. }
  782. /**
  783. * Slashes
  784. */
  785. if (value === '/') {
  786. // if the beginning of the glob is "./", advance the start
  787. // to the current index, and don't add the "./" characters
  788. // to the state. This greatly simplifies lookbehinds when
  789. // checking for BOS characters like "!" and "." (not "./")
  790. if (prev.type === 'dot' && state.index === state.start + 1) {
  791. state.start = state.index + 1;
  792. state.consumed = '';
  793. state.output = '';
  794. tokens.pop();
  795. prev = bos; // reset "prev" to the first token
  796. continue;
  797. }
  798. push({ type: 'slash', value, output: SLASH_LITERAL });
  799. continue;
  800. }
  801. /**
  802. * Dots
  803. */
  804. if (value === '.') {
  805. if (state.braces > 0 && prev.type === 'dot') {
  806. if (prev.value === '.') prev.output = DOT_LITERAL;
  807. const brace = braces[braces.length - 1];
  808. prev.type = 'dots';
  809. prev.output += value;
  810. prev.value += value;
  811. brace.dots = true;
  812. continue;
  813. }
  814. if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') {
  815. push({ type: 'text', value, output: DOT_LITERAL });
  816. continue;
  817. }
  818. push({ type: 'dot', value, output: DOT_LITERAL });
  819. continue;
  820. }
  821. /**
  822. * Question marks
  823. */
  824. if (value === '?') {
  825. const isGroup = prev && prev.value === '(';
  826. if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  827. extglobOpen('qmark', value);
  828. continue;
  829. }
  830. if (prev && prev.type === 'paren') {
  831. const next = peek();
  832. let output = value;
  833. if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) {
  834. output = `\\${value}`;
  835. }
  836. push({ type: 'text', value, output });
  837. continue;
  838. }
  839. if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) {
  840. push({ type: 'qmark', value, output: QMARK_NO_DOT });
  841. continue;
  842. }
  843. push({ type: 'qmark', value, output: QMARK });
  844. continue;
  845. }
  846. /**
  847. * Exclamation
  848. */
  849. if (value === '!') {
  850. if (opts.noextglob !== true && peek() === '(') {
  851. if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) {
  852. extglobOpen('negate', value);
  853. continue;
  854. }
  855. }
  856. if (opts.nonegate !== true && state.index === 0) {
  857. negate();
  858. continue;
  859. }
  860. }
  861. /**
  862. * Plus
  863. */
  864. if (value === '+') {
  865. if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  866. extglobOpen('plus', value);
  867. continue;
  868. }
  869. if ((prev && prev.value === '(') || opts.regex === false) {
  870. push({ type: 'plus', value, output: PLUS_LITERAL });
  871. continue;
  872. }
  873. if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) {
  874. push({ type: 'plus', value });
  875. continue;
  876. }
  877. push({ type: 'plus', value: PLUS_LITERAL });
  878. continue;
  879. }
  880. /**
  881. * Plain text
  882. */
  883. if (value === '@') {
  884. if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
  885. push({ type: 'at', extglob: true, value, output: '' });
  886. continue;
  887. }
  888. push({ type: 'text', value });
  889. continue;
  890. }
  891. /**
  892. * Plain text
  893. */
  894. if (value !== '*') {
  895. if (value === '$' || value === '^') {
  896. value = `\\${value}`;
  897. }
  898. const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
  899. if (match) {
  900. value += match[0];
  901. state.index += match[0].length;
  902. }
  903. push({ type: 'text', value });
  904. continue;
  905. }
  906. /**
  907. * Stars
  908. */
  909. if (prev && (prev.type === 'globstar' || prev.star === true)) {
  910. prev.type = 'star';
  911. prev.star = true;
  912. prev.value += value;
  913. prev.output = star;
  914. state.backtrack = true;
  915. state.globstar = true;
  916. consume(value);
  917. continue;
  918. }
  919. let rest = remaining();
  920. if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
  921. extglobOpen('star', value);
  922. continue;
  923. }
  924. if (prev.type === 'star') {
  925. if (opts.noglobstar === true) {
  926. consume(value);
  927. continue;
  928. }
  929. const prior = prev.prev;
  930. const before = prior.prev;
  931. const isStart = prior.type === 'slash' || prior.type === 'bos';
  932. const afterStar = before && (before.type === 'star' || before.type === 'globstar');
  933. if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) {
  934. push({ type: 'star', value, output: '' });
  935. continue;
  936. }
  937. const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace');
  938. const isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren');
  939. if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) {
  940. push({ type: 'star', value, output: '' });
  941. continue;
  942. }
  943. // strip consecutive `/**/`
  944. while (rest.slice(0, 3) === '/**') {
  945. const after = input[state.index + 4];
  946. if (after && after !== '/') {
  947. break;
  948. }
  949. rest = rest.slice(3);
  950. consume('/**', 3);
  951. }
  952. // A globstar followed only by balanced closing parens is at the logical end of patterns like
  953. // `test(/utils/**)` and `test?(/utils/**)`. Treat it as EOS so the trailing `/**` can match its
  954. // parent path, except in negated extglobs where that would change the exclusion semantics.
  955. const isEnd = eos() || (
  956. state.parens > 0
  957. && rest === ')'.repeat(state.parens)
  958. && !extglobs.some(extglob => extglob.type === 'negate')
  959. );
  960. if (prior.type === 'bos' && eos()) {
  961. prev.type = 'globstar';
  962. prev.value += value;
  963. prev.output = globstar(opts);
  964. state.output = prev.output;
  965. state.globstar = true;
  966. consume(value);
  967. continue;
  968. }
  969. if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && isEnd) {
  970. state.output = state.output.slice(0, -(prior.output + prev.output).length);
  971. prior.output = `(?:${prior.output}`;
  972. prev.type = 'globstar';
  973. prev.output = globstar(opts) + (opts.strictSlashes ? ')' : '|$)');
  974. prev.value += value;
  975. state.globstar = true;
  976. state.output += prior.output + prev.output;
  977. consume(value);
  978. continue;
  979. }
  980. if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') {
  981. const end = rest[1] !== void 0 ? '|$' : '';
  982. state.output = state.output.slice(0, -(prior.output + prev.output).length);
  983. prior.output = `(?:${prior.output}`;
  984. prev.type = 'globstar';
  985. prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
  986. prev.value += value;
  987. state.output += prior.output + prev.output;
  988. state.globstar = true;
  989. consume(value + advance());
  990. push({ type: 'slash', value: '/', output: '' });
  991. continue;
  992. }
  993. if (prior.type === 'bos' && rest[0] === '/') {
  994. prev.type = 'globstar';
  995. prev.value += value;
  996. prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
  997. state.output = prev.output;
  998. state.globstar = true;
  999. consume(value + advance());
  1000. push({ type: 'slash', value: '/', output: '' });
  1001. continue;
  1002. }
  1003. // remove single star from output
  1004. state.output = state.output.slice(0, -prev.output.length);
  1005. // reset previous token to globstar
  1006. prev.type = 'globstar';
  1007. prev.output = globstar(opts);
  1008. prev.value += value;
  1009. // reset output with globstar
  1010. state.output += prev.output;
  1011. state.globstar = true;
  1012. consume(value);
  1013. continue;
  1014. }
  1015. const token = { type: 'star', value, output: star };
  1016. if (opts.bash === true) {
  1017. token.output = '.*?';
  1018. if (prev.type === 'bos' || prev.type === 'slash') {
  1019. token.output = nodot + token.output;
  1020. }
  1021. push(token);
  1022. continue;
  1023. }
  1024. if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) {
  1025. token.output = value;
  1026. push(token);
  1027. continue;
  1028. }
  1029. if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') {
  1030. if (prev.type === 'dot') {
  1031. state.output += NO_DOT_SLASH;
  1032. prev.output += NO_DOT_SLASH;
  1033. } else if (opts.dot === true) {
  1034. state.output += NO_DOTS_SLASH;
  1035. prev.output += NO_DOTS_SLASH;
  1036. } else {
  1037. state.output += nodot;
  1038. prev.output += nodot;
  1039. }
  1040. if (peek() !== '*') {
  1041. state.output += ONE_CHAR;
  1042. prev.output += ONE_CHAR;
  1043. }
  1044. }
  1045. push(token);
  1046. }
  1047. while (state.brackets > 0) {
  1048. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']'));
  1049. state.output = utils.escapeLast(state.output, '[');
  1050. decrement('brackets');
  1051. }
  1052. while (state.parens > 0) {
  1053. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')'));
  1054. state.output = utils.escapeLast(state.output, '(');
  1055. decrement('parens');
  1056. }
  1057. while (state.braces > 0) {
  1058. if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}'));
  1059. state.output = utils.escapeLast(state.output, '{');
  1060. decrement('braces');
  1061. }
  1062. if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) {
  1063. push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` });
  1064. }
  1065. // rebuild the output if we had to backtrack at any point
  1066. if (state.backtrack === true) {
  1067. state.output = '';
  1068. for (const token of state.tokens) {
  1069. state.output += token.output != null ? token.output : token.value;
  1070. if (token.suffix) {
  1071. state.output += token.suffix;
  1072. }
  1073. }
  1074. }
  1075. return state;
  1076. };
  1077. /**
  1078. * Fast paths for creating regular expressions for common glob patterns.
  1079. * This can significantly speed up processing and has very little downside
  1080. * impact when none of the fast paths match.
  1081. */
  1082. parse.fastpaths = (input, options) => {
  1083. const opts = { ...options };
  1084. const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
  1085. const len = input.length;
  1086. if (len > max) {
  1087. throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
  1088. }
  1089. input = REPLACEMENTS[input] || input;
  1090. // create constants based on platform, for windows or posix
  1091. const {
  1092. DOT_LITERAL,
  1093. SLASH_LITERAL,
  1094. ONE_CHAR,
  1095. DOTS_SLASH,
  1096. NO_DOT,
  1097. NO_DOTS,
  1098. NO_DOTS_SLASH,
  1099. STAR,
  1100. START_ANCHOR
  1101. } = constants.globChars(opts.windows);
  1102. const nodot = opts.dot ? NO_DOTS : NO_DOT;
  1103. const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
  1104. const capture = opts.capture ? '' : '?:';
  1105. const state = { negated: false, prefix: '' };
  1106. let star = opts.bash === true ? '.*?' : STAR;
  1107. if (opts.capture) {
  1108. star = `(${star})`;
  1109. }
  1110. const globstar = opts => {
  1111. if (opts.noglobstar === true) return star;
  1112. return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
  1113. };
  1114. const create = str => {
  1115. switch (str) {
  1116. case '*':
  1117. return `${nodot}${ONE_CHAR}${star}`;
  1118. case '.*':
  1119. return `${DOT_LITERAL}${ONE_CHAR}${star}`;
  1120. case '*.*':
  1121. return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
  1122. case '*/*':
  1123. return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
  1124. case '**':
  1125. return nodot + globstar(opts);
  1126. case '**/*':
  1127. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
  1128. case '**/*.*':
  1129. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
  1130. case '**/.*':
  1131. return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
  1132. default: {
  1133. const match = /^(.*?)\.(\w+)$/.exec(str);
  1134. if (!match) return;
  1135. const source = create(match[1]);
  1136. if (!source) return;
  1137. return source + DOT_LITERAL + match[2];
  1138. }
  1139. }
  1140. };
  1141. const output = utils.removePrefix(input, state);
  1142. let source = create(output);
  1143. if (source && opts.strictSlashes !== true) {
  1144. source += `${SLASH_LITERAL}?`;
  1145. }
  1146. return source;
  1147. };
  1148. module.exports = parse;