123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092 |
- 'use strict';
- const constants = require('./constants');
- const utils = require('./utils');
- /**
- * Constants
- */
- const {
- MAX_LENGTH,
- POSIX_REGEX_SOURCE,
- REGEX_NON_SPECIAL_CHARS,
- REGEX_SPECIAL_CHARS_BACKREF,
- REPLACEMENTS
- } = constants;
- /**
- * Helpers
- */
- const expandRange = (args, options) => {
- if (typeof options.expandRange === 'function') {
- return options.expandRange(...args, options);
- }
- args.sort();
- const value = `[${args.join('-')}]`;
- try {
- /* eslint-disable-next-line no-new */
- new RegExp(value);
- } catch (ex) {
- return args.map(v => utils.escapeRegex(v)).join('..');
- }
- return value;
- };
- /**
- * Create the message for a syntax error
- */
- const syntaxError = (type, char) => {
- return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`;
- };
- /**
- * Parse the given input string.
- * @param {String} input
- * @param {Object} options
- * @return {Object}
- */
- const parse = (input, options) => {
- if (typeof input !== 'string') {
- throw new TypeError('Expected a string');
- }
- input = REPLACEMENTS[input] || input;
- const opts = { ...options };
- const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
- let len = input.length;
- if (len > max) {
- throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
- }
- const bos = { type: 'bos', value: '', output: opts.prepend || '' };
- const tokens = [bos];
- const capture = opts.capture ? '' : '?:';
- const win32 = utils.isWindows(options);
- // create constants based on platform, for windows or posix
- const PLATFORM_CHARS = constants.globChars(win32);
- const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS);
- const {
- DOT_LITERAL,
- PLUS_LITERAL,
- SLASH_LITERAL,
- ONE_CHAR,
- DOTS_SLASH,
- NO_DOT,
- NO_DOT_SLASH,
- NO_DOTS_SLASH,
- QMARK,
- QMARK_NO_DOT,
- STAR,
- START_ANCHOR
- } = PLATFORM_CHARS;
- const globstar = opts => {
- return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
- };
- const nodot = opts.dot ? '' : NO_DOT;
- const qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT;
- let star = opts.bash === true ? globstar(opts) : STAR;
- if (opts.capture) {
- star = `(${star})`;
- }
- // minimatch options support
- if (typeof opts.noext === 'boolean') {
- opts.noextglob = opts.noext;
- }
- const state = {
- input,
- index: -1,
- start: 0,
- dot: opts.dot === true,
- consumed: '',
- output: '',
- prefix: '',
- backtrack: false,
- negated: false,
- brackets: 0,
- braces: 0,
- parens: 0,
- quotes: 0,
- globstar: false,
- tokens
- };
- input = utils.removePrefix(input, state);
- len = input.length;
- const extglobs = [];
- const braces = [];
- const stack = [];
- let prev = bos;
- let value;
- /**
- * Tokenizing helpers
- */
- const eos = () => state.index === len - 1;
- const peek = state.peek = (n = 1) => input[state.index + n];
- const advance = state.advance = () => input[++state.index] || '';
- const remaining = () => input.slice(state.index + 1);
- const consume = (value = '', num = 0) => {
- state.consumed += value;
- state.index += num;
- };
- const append = token => {
- state.output += token.output != null ? token.output : token.value;
- consume(token.value);
- };
- const negate = () => {
- let count = 1;
- while (peek() === '!' && (peek(2) !== '(' || peek(3) === '?')) {
- advance();
- state.start++;
- count++;
- }
- if (count % 2 === 0) {
- return false;
- }
- state.negated = true;
- state.start++;
- return true;
- };
- const increment = type => {
- state[type]++;
- stack.push(type);
- };
- const decrement = type => {
- state[type]--;
- stack.pop();
- };
- /**
- * Push tokens onto the tokens array. This helper speeds up
- * tokenizing by 1) helping us avoid backtracking as much as possible,
- * and 2) helping us avoid creating extra tokens when consecutive
- * characters are plain text. This improves performance and simplifies
- * lookbehinds.
- */
- const push = tok => {
- if (prev.type === 'globstar') {
- const isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace');
- const isExtglob = tok.extglob === true || (extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'));
- if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) {
- state.output = state.output.slice(0, -prev.output.length);
- prev.type = 'star';
- prev.value = '*';
- prev.output = star;
- state.output += prev.output;
- }
- }
- if (extglobs.length && tok.type !== 'paren') {
- extglobs[extglobs.length - 1].inner += tok.value;
- }
- if (tok.value || tok.output) append(tok);
- if (prev && prev.type === 'text' && tok.type === 'text') {
- prev.value += tok.value;
- prev.output = (prev.output || '') + tok.value;
- return;
- }
- tok.prev = prev;
- tokens.push(tok);
- prev = tok;
- };
- const extglobOpen = (type, value) => {
- const token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' };
- token.prev = prev;
- token.parens = state.parens;
- token.output = state.output;
- const output = (opts.capture ? '(' : '') + token.open;
- increment('parens');
- push({ type, value, output: state.output ? '' : ONE_CHAR });
- push({ type: 'paren', extglob: true, value: advance(), output });
- extglobs.push(token);
- };
- const extglobClose = token => {
- let output = token.close + (opts.capture ? ')' : '');
- let rest;
- if (token.type === 'negate') {
- let extglobStar = star;
- if (token.inner && token.inner.length > 1 && token.inner.includes('/')) {
- extglobStar = globstar(opts);
- }
- if (extglobStar !== star || eos() || /^\)+$/.test(remaining())) {
- output = token.close = `)$))${extglobStar}`;
- }
- if (token.inner.includes('*') && (rest = remaining()) && /^\.[^\\/.]+$/.test(rest)) {
- // Any non-magical string (`.ts`) or even nested expression (`.{ts,tsx}`) can follow after the closing parenthesis.
- // In this case, we need to parse the string and use it in the output of the original pattern.
- // Suitable patterns: `/!(*.d).ts`, `/!(*.d).{ts,tsx}`, `**/!(*-dbg).@(js)`.
- //
- // Disabling the `fastpaths` option due to a problem with parsing strings as `.ts` in the pattern like `**/!(*.d).ts`.
- const expression = parse(rest, { ...options, fastpaths: false }).output;
- output = token.close = `)${expression})${extglobStar})`;
- }
- if (token.prev.type === 'bos') {
- state.negatedExtglob = true;
- }
- }
- push({ type: 'paren', extglob: true, value, output });
- decrement('parens');
- };
- /**
- * Fast paths
- */
- if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
- let backslashes = false;
- let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
- if (first === '\\') {
- backslashes = true;
- return m;
- }
- if (first === '?') {
- if (esc) {
- return esc + first + (rest ? QMARK.repeat(rest.length) : '');
- }
- if (index === 0) {
- return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : '');
- }
- return QMARK.repeat(chars.length);
- }
- if (first === '.') {
- return DOT_LITERAL.repeat(chars.length);
- }
- if (first === '*') {
- if (esc) {
- return esc + first + (rest ? star : '');
- }
- return star;
- }
- return esc ? m : `\\${m}`;
- });
- if (backslashes === true) {
- if (opts.unescape === true) {
- output = output.replace(/\\/g, '');
- } else {
- output = output.replace(/\\+/g, m => {
- return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : '');
- });
- }
- }
- if (output === input && opts.contains === true) {
- state.output = input;
- return state;
- }
- state.output = utils.wrapOutput(output, state, options);
- return state;
- }
- /**
- * Tokenize input until we reach end-of-string
- */
- while (!eos()) {
- value = advance();
- if (value === '\u0000') {
- continue;
- }
- /**
- * Escaped characters
- */
- if (value === '\\') {
- const next = peek();
- if (next === '/' && opts.bash !== true) {
- continue;
- }
- if (next === '.' || next === ';') {
- continue;
- }
- if (!next) {
- value += '\\';
- push({ type: 'text', value });
- continue;
- }
- // collapse slashes to reduce potential for exploits
- const match = /^\\+/.exec(remaining());
- let slashes = 0;
- if (match && match[0].length > 2) {
- slashes = match[0].length;
- state.index += slashes;
- if (slashes % 2 !== 0) {
- value += '\\';
- }
- }
- if (opts.unescape === true) {
- value = advance();
- } else {
- value += advance();
- }
- if (state.brackets === 0) {
- push({ type: 'text', value });
- continue;
- }
- }
- /**
- * If we're inside a regex character class, continue
- * until we reach the closing bracket.
- */
- if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) {
- if (opts.posix !== false && value === ':') {
- const inner = prev.value.slice(1);
- if (inner.includes('[')) {
- prev.posix = true;
- if (inner.includes(':')) {
- const idx = prev.value.lastIndexOf('[');
- const pre = prev.value.slice(0, idx);
- const rest = prev.value.slice(idx + 2);
- const posix = POSIX_REGEX_SOURCE[rest];
- if (posix) {
- prev.value = pre + posix;
- state.backtrack = true;
- advance();
- if (!bos.output && tokens.indexOf(prev) === 1) {
- bos.output = ONE_CHAR;
- }
- continue;
- }
- }
- }
- }
- if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) {
- value = `\\${value}`;
- }
- if (value === ']' && (prev.value === '[' || prev.value === '[^')) {
- value = `\\${value}`;
- }
- if (opts.posix === true && value === '!' && prev.value === '[') {
- value = '^';
- }
- prev.value += value;
- append({ value });
- continue;
- }
- /**
- * If we're inside a quoted string, continue
- * until we reach the closing double quote.
- */
- if (state.quotes === 1 && value !== '"') {
- value = utils.escapeRegex(value);
- prev.value += value;
- append({ value });
- continue;
- }
- /**
- * Double quotes
- */
- if (value === '"') {
- state.quotes = state.quotes === 1 ? 0 : 1;
- if (opts.keepQuotes === true) {
- push({ type: 'text', value });
- }
- continue;
- }
- /**
- * Parentheses
- */
- if (value === '(') {
- increment('parens');
- push({ type: 'paren', value });
- continue;
- }
- if (value === ')') {
- if (state.parens === 0 && opts.strictBrackets === true) {
- throw new SyntaxError(syntaxError('opening', '('));
- }
- const extglob = extglobs[extglobs.length - 1];
- if (extglob && state.parens === extglob.parens + 1) {
- extglobClose(extglobs.pop());
- continue;
- }
- push({ type: 'paren', value, output: state.parens ? ')' : '\\)' });
- decrement('parens');
- continue;
- }
- /**
- * Square brackets
- */
- if (value === '[') {
- if (opts.nobracket === true || !remaining().includes(']')) {
- if (opts.nobracket !== true && opts.strictBrackets === true) {
- throw new SyntaxError(syntaxError('closing', ']'));
- }
- value = `\\${value}`;
- } else {
- increment('brackets');
- }
- push({ type: 'bracket', value });
- continue;
- }
- if (value === ']') {
- if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) {
- push({ type: 'text', value, output: `\\${value}` });
- continue;
- }
- if (state.brackets === 0) {
- if (opts.strictBrackets === true) {
- throw new SyntaxError(syntaxError('opening', '['));
- }
- push({ type: 'text', value, output: `\\${value}` });
- continue;
- }
- decrement('brackets');
- const prevValue = prev.value.slice(1);
- if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) {
- value = `/${value}`;
- }
- prev.value += value;
- append({ value });
- // when literal brackets are explicitly disabled
- // assume we should match with a regex character class
- if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) {
- continue;
- }
- const escaped = utils.escapeRegex(prev.value);
- state.output = state.output.slice(0, -prev.value.length);
- // when literal brackets are explicitly enabled
- // assume we should escape the brackets to match literal characters
- if (opts.literalBrackets === true) {
- state.output += escaped;
- prev.value = escaped;
- continue;
- }
- // when the user specifies nothing, try to match both
- prev.value = `(${capture}${escaped}|${prev.value})`;
- state.output += prev.value;
- continue;
- }
- /**
- * Braces
- */
- if (value === '{' && opts.nobrace !== true) {
- increment('braces');
- const open = {
- type: 'brace',
- value,
- output: '(',
- outputIndex: state.output.length,
- tokensIndex: state.tokens.length
- };
- braces.push(open);
- push(open);
- continue;
- }
- if (value === '}') {
- const brace = braces[braces.length - 1];
- if (opts.nobrace === true || !brace) {
- push({ type: 'text', value, output: value });
- continue;
- }
- let output = ')';
- if (brace.dots === true) {
- const arr = tokens.slice();
- const range = [];
- for (let i = arr.length - 1; i >= 0; i--) {
- tokens.pop();
- if (arr[i].type === 'brace') {
- break;
- }
- if (arr[i].type !== 'dots') {
- range.unshift(arr[i].value);
- }
- }
- output = expandRange(range, opts);
- state.backtrack = true;
- }
- if (brace.comma !== true && brace.dots !== true) {
- const out = state.output.slice(0, brace.outputIndex);
- const toks = state.tokens.slice(brace.tokensIndex);
- brace.value = brace.output = '\\{';
- value = output = '\\}';
- state.output = out;
- for (const t of toks) {
- state.output += (t.output || t.value);
- }
- }
- push({ type: 'brace', value, output });
- decrement('braces');
- braces.pop();
- continue;
- }
- /**
- * Pipes
- */
- if (value === '|') {
- if (extglobs.length > 0) {
- extglobs[extglobs.length - 1].conditions++;
- }
- push({ type: 'text', value });
- continue;
- }
- /**
- * Commas
- */
- if (value === ',') {
- let output = value;
- const brace = braces[braces.length - 1];
- if (brace && stack[stack.length - 1] === 'braces') {
- brace.comma = true;
- output = '|';
- }
- push({ type: 'comma', value, output });
- continue;
- }
- /**
- * Slashes
- */
- if (value === '/') {
- // if the beginning of the glob is "./", advance the start
- // to the current index, and don't add the "./" characters
- // to the state. This greatly simplifies lookbehinds when
- // checking for BOS characters like "!" and "." (not "./")
- if (prev.type === 'dot' && state.index === state.start + 1) {
- state.start = state.index + 1;
- state.consumed = '';
- state.output = '';
- tokens.pop();
- prev = bos; // reset "prev" to the first token
- continue;
- }
- push({ type: 'slash', value, output: SLASH_LITERAL });
- continue;
- }
- /**
- * Dots
- */
- if (value === '.') {
- if (state.braces > 0 && prev.type === 'dot') {
- if (prev.value === '.') prev.output = DOT_LITERAL;
- const brace = braces[braces.length - 1];
- prev.type = 'dots';
- prev.output += value;
- prev.value += value;
- brace.dots = true;
- continue;
- }
- if ((state.braces + state.parens) === 0 && prev.type !== 'bos' && prev.type !== 'slash') {
- push({ type: 'text', value, output: DOT_LITERAL });
- continue;
- }
- push({ type: 'dot', value, output: DOT_LITERAL });
- continue;
- }
- /**
- * Question marks
- */
- if (value === '?') {
- const isGroup = prev && prev.value === '(';
- if (!isGroup && opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
- extglobOpen('qmark', value);
- continue;
- }
- if (prev && prev.type === 'paren') {
- const next = peek();
- let output = value;
- if (next === '<' && !utils.supportsLookbehinds()) {
- throw new Error('Node.js v10 or higher is required for regex lookbehinds');
- }
- if ((prev.value === '(' && !/[!=<:]/.test(next)) || (next === '<' && !/<([!=]|\w+>)/.test(remaining()))) {
- output = `\\${value}`;
- }
- push({ type: 'text', value, output });
- continue;
- }
- if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) {
- push({ type: 'qmark', value, output: QMARK_NO_DOT });
- continue;
- }
- push({ type: 'qmark', value, output: QMARK });
- continue;
- }
- /**
- * Exclamation
- */
- if (value === '!') {
- if (opts.noextglob !== true && peek() === '(') {
- if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) {
- extglobOpen('negate', value);
- continue;
- }
- }
- if (opts.nonegate !== true && state.index === 0) {
- negate();
- continue;
- }
- }
- /**
- * Plus
- */
- if (value === '+') {
- if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
- extglobOpen('plus', value);
- continue;
- }
- if ((prev && prev.value === '(') || opts.regex === false) {
- push({ type: 'plus', value, output: PLUS_LITERAL });
- continue;
- }
- if ((prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) || state.parens > 0) {
- push({ type: 'plus', value });
- continue;
- }
- push({ type: 'plus', value: PLUS_LITERAL });
- continue;
- }
- /**
- * Plain text
- */
- if (value === '@') {
- if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') {
- push({ type: 'at', extglob: true, value, output: '' });
- continue;
- }
- push({ type: 'text', value });
- continue;
- }
- /**
- * Plain text
- */
- if (value !== '*') {
- if (value === '$' || value === '^') {
- value = `\\${value}`;
- }
- const match = REGEX_NON_SPECIAL_CHARS.exec(remaining());
- if (match) {
- value += match[0];
- state.index += match[0].length;
- }
- push({ type: 'text', value });
- continue;
- }
- /**
- * Stars
- */
- if (prev && (prev.type === 'globstar' || prev.star === true)) {
- prev.type = 'star';
- prev.star = true;
- prev.value += value;
- prev.output = star;
- state.backtrack = true;
- state.globstar = true;
- consume(value);
- continue;
- }
- let rest = remaining();
- if (opts.noextglob !== true && /^\([^?]/.test(rest)) {
- extglobOpen('star', value);
- continue;
- }
- if (prev.type === 'star') {
- if (opts.noglobstar === true) {
- consume(value);
- continue;
- }
- const prior = prev.prev;
- const before = prior.prev;
- const isStart = prior.type === 'slash' || prior.type === 'bos';
- const afterStar = before && (before.type === 'star' || before.type === 'globstar');
- if (opts.bash === true && (!isStart || (rest[0] && rest[0] !== '/'))) {
- push({ type: 'star', value, output: '' });
- continue;
- }
- const isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace');
- const isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren');
- if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) {
- push({ type: 'star', value, output: '' });
- continue;
- }
- // strip consecutive `/**/`
- while (rest.slice(0, 3) === '/**') {
- const after = input[state.index + 4];
- if (after && after !== '/') {
- break;
- }
- rest = rest.slice(3);
- consume('/**', 3);
- }
- if (prior.type === 'bos' && eos()) {
- prev.type = 'globstar';
- prev.value += value;
- prev.output = globstar(opts);
- state.output = prev.output;
- state.globstar = true;
- consume(value);
- continue;
- }
- if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) {
- state.output = state.output.slice(0, -(prior.output + prev.output).length);
- prior.output = `(?:${prior.output}`;
- prev.type = 'globstar';
- prev.output = globstar(opts) + (opts.strictSlashes ? ')' : '|$)');
- prev.value += value;
- state.globstar = true;
- state.output += prior.output + prev.output;
- consume(value);
- continue;
- }
- if (prior.type === 'slash' && prior.prev.type !== 'bos' && rest[0] === '/') {
- const end = rest[1] !== void 0 ? '|$' : '';
- state.output = state.output.slice(0, -(prior.output + prev.output).length);
- prior.output = `(?:${prior.output}`;
- prev.type = 'globstar';
- prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`;
- prev.value += value;
- state.output += prior.output + prev.output;
- state.globstar = true;
- consume(value + advance());
- push({ type: 'slash', value: '/', output: '' });
- continue;
- }
- if (prior.type === 'bos' && rest[0] === '/') {
- prev.type = 'globstar';
- prev.value += value;
- prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`;
- state.output = prev.output;
- state.globstar = true;
- consume(value + advance());
- push({ type: 'slash', value: '/', output: '' });
- continue;
- }
- // remove single star from output
- state.output = state.output.slice(0, -prev.output.length);
- // reset previous token to globstar
- prev.type = 'globstar';
- prev.output = globstar(opts);
- prev.value += value;
- // reset output with globstar
- state.output += prev.output;
- state.globstar = true;
- consume(value);
- continue;
- }
- const token = { type: 'star', value, output: star };
- if (opts.bash === true) {
- token.output = '.*?';
- if (prev.type === 'bos' || prev.type === 'slash') {
- token.output = nodot + token.output;
- }
- push(token);
- continue;
- }
- if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) {
- token.output = value;
- push(token);
- continue;
- }
- if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') {
- if (prev.type === 'dot') {
- state.output += NO_DOT_SLASH;
- prev.output += NO_DOT_SLASH;
- } else if (opts.dot === true) {
- state.output += NO_DOTS_SLASH;
- prev.output += NO_DOTS_SLASH;
- } else {
- state.output += nodot;
- prev.output += nodot;
- }
- if (peek() !== '*') {
- state.output += ONE_CHAR;
- prev.output += ONE_CHAR;
- }
- }
- push(token);
- }
- while (state.brackets > 0) {
- if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']'));
- state.output = utils.escapeLast(state.output, '[');
- decrement('brackets');
- }
- while (state.parens > 0) {
- if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')'));
- state.output = utils.escapeLast(state.output, '(');
- decrement('parens');
- }
- while (state.braces > 0) {
- if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}'));
- state.output = utils.escapeLast(state.output, '{');
- decrement('braces');
- }
- if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) {
- push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` });
- }
- // rebuild the output if we had to backtrack at any point
- if (state.backtrack === true) {
- state.output = '';
- for (const token of state.tokens) {
- state.output += token.output != null ? token.output : token.value;
- if (token.suffix) {
- state.output += token.suffix;
- }
- }
- }
- return state;
- };
- /**
- * Fast paths for creating regular expressions for common glob patterns.
- * This can significantly speed up processing and has very little downside
- * impact when none of the fast paths match.
- */
- parse.fastpaths = (input, options) => {
- const opts = { ...options };
- const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
- const len = input.length;
- if (len > max) {
- throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
- }
- input = REPLACEMENTS[input] || input;
- const win32 = utils.isWindows(options);
- // create constants based on platform, for windows or posix
- const {
- DOT_LITERAL,
- SLASH_LITERAL,
- ONE_CHAR,
- DOTS_SLASH,
- NO_DOT,
- NO_DOTS,
- NO_DOTS_SLASH,
- STAR,
- START_ANCHOR
- } = constants.globChars(win32);
- const nodot = opts.dot ? NO_DOTS : NO_DOT;
- const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
- const capture = opts.capture ? '' : '?:';
- const state = { negated: false, prefix: '' };
- let star = opts.bash === true ? '.*?' : STAR;
- if (opts.capture) {
- star = `(${star})`;
- }
- const globstar = opts => {
- if (opts.noglobstar === true) return star;
- return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`;
- };
- const create = str => {
- switch (str) {
- case '*':
- return `${nodot}${ONE_CHAR}${star}`;
- case '.*':
- return `${DOT_LITERAL}${ONE_CHAR}${star}`;
- case '*.*':
- return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
- case '*/*':
- return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`;
- case '**':
- return nodot + globstar(opts);
- case '**/*':
- return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`;
- case '**/*.*':
- return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`;
- case '**/.*':
- return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`;
- default: {
- const match = /^(.*?)\.(\w+)$/.exec(str);
- if (!match) return;
- const source = create(match[1]);
- if (!source) return;
- return source + DOT_LITERAL + match[2];
- }
- }
- };
- const output = utils.removePrefix(input, state);
- let source = create(output);
- if (source && opts.strictSlashes !== true) {
- source += `${SLASH_LITERAL}?`;
- }
- return source;
- };
- module.exports = parse;
|