| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.toMatcher = exports.toRegex = void 0;
- const escapeRe = (ch) => (/[.^$+{}()|\\]/.test(ch) ? `\\${ch}` : ch);
- /**
- * Parse an extended glob pattern like ?(a|b|c)
- * Returns the regex string equivalent and the new index position
- */
- const parseExtGlob = (pattern, startIdx, prefix, options) => {
- let i = startIdx; // startIdx should be pointing at the character after '('
- const parts = [];
- let cur = '';
- let depth = 1; // Track parenthesis depth for nested patterns
- while (i < pattern.length && depth > 0) {
- const ch = pattern[i];
- if (ch === '(') {
- depth++;
- cur += ch;
- i++;
- }
- else if (ch === ')') {
- depth--;
- if (depth === 0) {
- // Found the closing parenthesis
- parts.push(cur);
- i++; // consume ')'
- break;
- }
- else {
- cur += ch;
- i++;
- }
- }
- else if (ch === '|' && depth === 1) {
- // Pipe separator at top level of this extglob
- parts.push(cur);
- cur = '';
- i++;
- }
- else {
- cur += ch;
- i++;
- }
- }
- if (depth !== 0)
- return; // Unclosed parenthesis
- let alternatives = '';
- const length = parts.length;
- for (let j = 0; j < length; j++)
- alternatives += (alternatives ? '|' : '') + (0, exports.toRegex)(parts[j], options).source.replace(/^\^/, '').replace(/\$$/, '');
- switch (prefix) {
- case '?': // zero or one
- return [`(?:${alternatives})?`, i];
- case '*': // zero or more
- return [`(?:${alternatives})*`, i];
- case '+': // one or more
- return [`(?:${alternatives})+`, i];
- case '@': // exactly one
- return [`(?:${alternatives})`, i];
- case '!': // none of (negative match)
- // For negation, we need to match anything that doesn't match the pattern
- // Use negative lookahead without consuming characters after
- return [`(?!${alternatives})[^/]*`, i];
- }
- return;
- };
- /**
- * Convert a glob pattern to a regular expression
- *
- * Supports:
- * - `/` to separate path segments
- * - `*` to match zero or more characters in a path segment
- * - `?` to match one character in a path segment
- * - `**` to match any number of path segments, including none
- * - `{}` to group conditions (e.g. `{html,txt}`)
- * - `[abc]`, `[a-z]`, `[!a-z]`, `[!abc]` character classes
- * - Extended globbing (when `extglob: true` option is set):
- * - `?(pattern-list)` zero or one occurrence
- * - `*(pattern-list)` zero or more occurrences
- * - `+(pattern-list)` one or more occurrences
- * - `@(pattern-list)` exactly one of the patterns
- * - `!(pattern-list)` anything except the patterns
- */
- const toRegex = (pattern, options) => {
- let regexStr = '';
- let i = 0;
- // Helper to parse a brace group like {a,b,c}. No nesting support.
- const parseBraceGroup = () => {
- // Assume current char is '{'
- i++; // skip '{'
- const parts = [];
- let cur = '';
- let closed = false;
- while (i < pattern.length) {
- const ch = pattern[i];
- if (ch === '}') {
- parts.push(cur);
- i++; // consume '}'
- closed = true;
- break;
- }
- if (ch === ',') {
- parts.push(cur);
- cur = '';
- i++;
- continue;
- }
- cur += ch;
- i++;
- }
- if (!closed) {
- // treat as literal '{...'
- return '\\{' + escapeRe(cur);
- }
- // Convert each part recursively to support globs inside braces
- const alt = parts.map((p) => (0, exports.toRegex)(p, options).source.replace(/^\^/, '').replace(/\$$/, '')).join('|');
- return `(?:${alt})`;
- };
- const extglob = !!options?.extglob;
- while (i < pattern.length) {
- const char = pattern[i];
- // Check for extended glob patterns when extglob is enabled
- if (extglob && pattern[i + 1] === '(') {
- if (char === '?' || char === '*' || char === '+' || char === '@' || char === '!') {
- const result = parseExtGlob(pattern, i + 2, char, options);
- if (result) {
- regexStr += result[0];
- i = result[1];
- continue;
- }
- // If parse failed, fall through to normal handling
- }
- }
- switch (char) {
- case '*': {
- // Check for double star **
- if (pattern[i + 1] === '*') {
- // Collapse consecutive * beyond two (e.g., *** -> **)
- let j = i + 2;
- while (pattern[j] === '*')
- j++;
- // If followed by a slash, make it optional to allow zero segments
- if (pattern[j] === '/') {
- regexStr += '(?:.*/)?';
- i = j + 1; // consume **/
- }
- else {
- regexStr += '.*';
- i = j; // consume **
- }
- }
- else {
- regexStr += '[^/]*';
- i++;
- }
- break;
- }
- case '?':
- regexStr += '[^/]';
- i++;
- break;
- case '[': {
- // Copy character class as-is with support for leading '!'
- let cls = '[';
- i++;
- if (i < pattern.length && pattern[i] === '!') {
- cls += '^';
- i++;
- }
- // if first after [ or [^ is ']' include it literally
- if (i < pattern.length && pattern[i] === ']') {
- cls += ']';
- i++;
- }
- while (i < pattern.length && pattern[i] !== ']') {
- const ch = pattern[i];
- // Escape backslash inside class
- cls += ch === '\\' ? '\\\\' : ch;
- i++;
- }
- if (i < pattern.length && pattern[i] === ']') {
- cls += ']';
- i++;
- }
- else {
- // Unclosed class -> treat '[' literally
- regexStr += '\\[';
- continue;
- }
- regexStr += cls;
- break;
- }
- case '{': {
- regexStr += parseBraceGroup();
- break;
- }
- case '/':
- regexStr += '/';
- i++;
- break;
- case '.':
- case '^':
- case '$':
- case '+':
- case '(':
- case ')':
- case '|':
- case '\\':
- regexStr += `\\${char}`;
- i++;
- break;
- default:
- regexStr += char;
- i++;
- break;
- }
- }
- const flags = options?.nocase ? 'i' : '';
- return new RegExp('^' + regexStr + '$', flags);
- };
- exports.toRegex = toRegex;
- const isRegExp = /^\/(.{1,4096})\/([gimsuy]{0,6})$/;
- const toMatcher = (pattern, options) => {
- const regexes = [];
- const patterns = Array.isArray(pattern) ? pattern : [pattern];
- for (const pat of patterns) {
- if (typeof pat === 'string') {
- const match = isRegExp.exec(pat);
- if (match) {
- const [, expr, flags] = match;
- regexes.push(new RegExp(expr, flags));
- }
- else {
- regexes.push((0, exports.toRegex)(pat, options));
- }
- }
- else {
- regexes.push(pat);
- }
- }
- return regexes.length
- ? new Function('p', 'return ' + regexes.map((r) => r + '.test(p)').join('||'))
- : () => false;
- };
- exports.toMatcher = toMatcher;
|