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